diff options
Diffstat (limited to 'src/java/org/apache/fop')
210 files changed, 14599 insertions, 3741 deletions
diff --git a/src/java/org/apache/fop/afp/AFPDataObjectInfo.java b/src/java/org/apache/fop/afp/AFPDataObjectInfo.java index 011118683..158189b76 100644 --- a/src/java/org/apache/fop/afp/AFPDataObjectInfo.java +++ b/src/java/org/apache/fop/afp/AFPDataObjectInfo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp; @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.afp.modca.Registry; +import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; /** * A list of parameters associated with an AFP data objects @@ -57,6 +58,9 @@ public class AFPDataObjectInfo { /** controls whether to create a page segment or a simple object */ private boolean createPageSegment; + /** controls the mapping of the image data into the image area */ + private byte mappingOption = MappingOptionTriplet.SCALE_TO_FILL; + /** * Default constructor */ @@ -253,6 +257,23 @@ public class AFPDataObjectInfo { return this.createPageSegment; } + /** + * Sets the way an image is mapped into its target area. + * @param mappingOption the mapping option (Valid values: see Mapping Option Triplet) + */ + public void setMappingOption(byte mappingOption) { + this.mappingOption = mappingOption; + } + + /** + * Returns the way an image is mapped into its target area. By default, this is "scale to fill" + * behavior. + * @return the mapping option value from the Mapping Option Triplet + */ + public byte getMappingOption() { + return mappingOption; + } + /** {@inheritDoc} */ public String toString() { return "AFPDataObjectInfo{" @@ -264,4 +285,5 @@ public class AFPDataObjectInfo { + (objectAreaInfo != null ? ", objectAreaInfo=" + objectAreaInfo : "") + (resourceInfo != null ? ", resourceInfo=" + resourceInfo : ""); } + } diff --git a/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java new file mode 100644 index 000000000..79e4979fd --- /dev/null +++ b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.afp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; +import org.apache.fop.util.bitmap.DitherUtil; + + +/** + * A painter of rectangles in AFP + */ +public class AFPDitheredRectanglePainter extends AbstractAFPPainter { + + private AFPResourceManager resourceManager; + + /** + * Main constructor + * + * @param paintingState the AFP painting state + * @param dataStream the AFP datastream + * @param resourceManager the resource manager + */ + public AFPDitheredRectanglePainter(AFPPaintingState paintingState, DataStream dataStream, + AFPResourceManager resourceManager) { + super(paintingState, dataStream); + this.resourceManager = resourceManager; + } + + /** {@inheritDoc} */ + public void paint(PaintingInfo paintInfo) throws IOException { + RectanglePaintingInfo rectanglePaintInfo = (RectanglePaintingInfo)paintInfo; + if (rectanglePaintInfo.getWidth() <= 0 || rectanglePaintInfo.getHeight() <= 0) { + return; + } + + int ditherMatrix = DitherUtil.DITHER_MATRIX_8X8; + Dimension ditherSize = new Dimension(ditherMatrix, ditherMatrix); + + //Prepare an FS10 bi-level image + AFPImageObjectInfo imageObjectInfo = new AFPImageObjectInfo(); + imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS10); + //imageObjectInfo.setCreatePageSegment(true); + imageObjectInfo.getResourceInfo().setLevel(new AFPResourceLevel(AFPResourceLevel.INLINE)); + imageObjectInfo.getResourceInfo().setImageDimension(ditherSize); + imageObjectInfo.setBitsPerPixel(1); + imageObjectInfo.setColor(false); + //Note: the following may not be supported by older implementations + imageObjectInfo.setMappingOption(MappingOptionTriplet.REPLICATE_AND_TRIM); + + //Dither image size + int resolution = paintingState.getResolution(); + ImageSize ditherBitmapSize = new ImageSize( + ditherSize.width, ditherSize.height, resolution); + imageObjectInfo.setDataHeightRes((int)Math.round( + ditherBitmapSize.getDpiHorizontal() * 10)); + imageObjectInfo.setDataWidthRes((int)Math.round( + ditherBitmapSize.getDpiVertical() * 10)); + imageObjectInfo.setDataWidth(ditherSize.width); + imageObjectInfo.setDataHeight(ditherSize.height); + + //Create dither image + Color col = paintingState.getColor(); + byte[] dither = DitherUtil.getBayerDither(ditherMatrix, col, false); + imageObjectInfo.setData(dither); + + //Positioning + AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(); + int rotation = paintingState.getRotation(); + AffineTransform at = paintingState.getData().getTransform(); + Point2D origin = at.transform(new Point2D.Float( + rectanglePaintInfo.getX() * 1000, + rectanglePaintInfo.getY() * 1000), null); + objectAreaInfo.setX((int)Math.round(origin.getX())); + objectAreaInfo.setY((int)Math.round(origin.getY())); + AFPUnitConverter unitConv = paintingState.getUnitConverter(); + float width = unitConv.pt2units(rectanglePaintInfo.getWidth()); + float height = unitConv.pt2units(rectanglePaintInfo.getHeight()); + objectAreaInfo.setWidth(Math.round(width)); + objectAreaInfo.setHeight(Math.round(height)); + objectAreaInfo.setHeightRes(resolution); + objectAreaInfo.setWidthRes(resolution); + objectAreaInfo.setRotation(rotation); + imageObjectInfo.setObjectAreaInfo(objectAreaInfo); + + //Create rectangle + resourceManager.createObject(imageObjectInfo); + } + +} diff --git a/src/java/org/apache/fop/afp/AFPEventProducer.java b/src/java/org/apache/fop/afp/AFPEventProducer.java index 49792183f..b56250fc2 100644 --- a/src/java/org/apache/fop/afp/AFPEventProducer.java +++ b/src/java/org/apache/fop/afp/AFPEventProducer.java @@ -80,4 +80,14 @@ public interface AFPEventProducer extends EventProducer { * @event.severity ERROR */ void characterSetEncodingError(Object source, String charSetName, String encoding); + + /** + * Triggered when an external resource fails to be embedded. + * + * @param source the event source + * @param resourceName the name of the resource where the error occurred + * @param e the original exception + * @event.severity ERROR + */ + void resourceEmbeddingError(Object source, String resourceName, Exception e); } diff --git a/src/java/org/apache/fop/afp/AFPEventProducer.xml b/src/java/org/apache/fop/afp/AFPEventProducer.xml index 8e6bb5429..364cd92ff 100644 --- a/src/java/org/apache/fop/afp/AFPEventProducer.xml +++ b/src/java/org/apache/fop/afp/AFPEventProducer.xml @@ -3,4 +3,5 @@ <message key="org.apache.fop.afp.AFPEventProducer.warnDefaultFontSetup">No AFP fonts configured. Using default setup.</message> <message key="org.apache.fop.afp.AFPEventProducer.warnMissingDefaultFont">No AFP default "any", {style}, {weight} font configured.</message> <message key="org.apache.fop.afp.AFPEventProducer.characterSetEncodingError">An error occurred when attempting to encode character set {charSetName} with encoding scheme {encoding}.</message> + <message key="org.apache.fop.afp.AFPEventProducer.resourceEmbeddingError">An error occurs while embedding the resource named "{resourceName}".[ Reason: {e}]</message> </catalogue> diff --git a/src/java/org/apache/fop/afp/AFPGraphics2D.java b/src/java/org/apache/fop/afp/AFPGraphics2D.java index 2e176e5ab..0e90c821f 100644 --- a/src/java/org/apache/fop/afp/AFPGraphics2D.java +++ b/src/java/org/apache/fop/afp/AFPGraphics2D.java @@ -62,6 +62,7 @@ import org.apache.xmlgraphics.util.UnitConv; import org.apache.fop.afp.goca.GraphicsSetLineType; import org.apache.fop.afp.modca.GraphicsObject; import org.apache.fop.afp.svg.AFPGraphicsConfiguration; +import org.apache.fop.afp.util.CubicBezierApproximator; import org.apache.fop.fonts.FontInfo; import org.apache.fop.svg.NativeImageHandler; @@ -437,6 +438,7 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand */ private void processPathIterator(PathIterator iter) { double[] dstPts = new double[6]; + double[] currentPosition = new double[2]; for (int[] openingCoords = new int[2]; !iter.isDone(); iter.next()) { switch (iter.currentSegment(dstPts)) { case PathIterator.SEG_LINETO: @@ -444,6 +446,7 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand (int)Math.round(dstPts[X]), (int)Math.round(dstPts[Y]) }, true); + currentPosition = new double[]{dstPts[X], dstPts[Y]}; break; case PathIterator.SEG_QUADTO: graphicsObj.addFillet(new int[] { @@ -452,26 +455,39 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand (int)Math.round(dstPts[X2]), (int)Math.round(dstPts[Y2]) }, true); + currentPosition = new double[]{dstPts[X2], dstPts[Y2]}; break; case PathIterator.SEG_CUBICTO: - graphicsObj.addFillet(new int[] { - (int)Math.round(dstPts[X1]), - (int)Math.round(dstPts[Y1]), - (int)Math.round(dstPts[X2]), - (int)Math.round(dstPts[Y2]), - (int)Math.round(dstPts[X3]), - (int)Math.round(dstPts[Y3]) - }, true); + double[] cubicCoords = new double[] {currentPosition[0], currentPosition[1], + dstPts[X1], dstPts[Y1], dstPts[X2], dstPts[Y2], dstPts[X3], dstPts[Y3]}; + double[][] quadParts = CubicBezierApproximator.fixedMidPointApproximation( + cubicCoords); + if (quadParts.length >= 4) { + for (int segIndex = 0; segIndex < quadParts.length; segIndex++) { + double[] quadPts = quadParts[segIndex]; + if (quadPts != null && quadPts.length == 4) { + graphicsObj.addFillet(new int[]{ + (int) Math.round(quadPts[X1]), + (int) Math.round(quadPts[Y1]), + (int) Math.round(quadPts[X2]), + (int) Math.round(quadPts[Y2]) + }, true); + currentPosition = new double[]{quadPts[X2], quadPts[Y2]}; + } + } + } break; case PathIterator.SEG_MOVETO: openingCoords = new int[] { (int)Math.round(dstPts[X]), (int)Math.round(dstPts[Y]) }; + currentPosition = new double[]{dstPts[X], dstPts[Y]}; graphicsObj.setCurrentPosition(openingCoords); break; case PathIterator.SEG_CLOSE: graphicsObj.addLine(openingCoords, true); + currentPosition = new double[]{openingCoords[0], openingCoords[1]}; break; default: log.debug("Unrecognised path iterator type"); diff --git a/src/java/org/apache/fop/afp/AFPImageObjectInfo.java b/src/java/org/apache/fop/afp/AFPImageObjectInfo.java index 45ea5fc1f..7aee3cda8 100644 --- a/src/java/org/apache/fop/afp/AFPImageObjectInfo.java +++ b/src/java/org/apache/fop/afp/AFPImageObjectInfo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp; diff --git a/src/java/org/apache/fop/afp/AFPResourceInfo.java b/src/java/org/apache/fop/afp/AFPResourceInfo.java index 43ba2a238..64623c3db 100644 --- a/src/java/org/apache/fop/afp/AFPResourceInfo.java +++ b/src/java/org/apache/fop/afp/AFPResourceInfo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp; diff --git a/src/java/org/apache/fop/afp/AFPResourceManager.java b/src/java/org/apache/fop/afp/AFPResourceManager.java index c44698a10..b7e1abc01 100644 --- a/src/java/org/apache/fop/afp/AFPResourceManager.java +++ b/src/java/org/apache/fop/afp/AFPResourceManager.java @@ -28,6 +28,8 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.afp.fonts.AFPFont; +import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.AbstractNamedAFPObject; import org.apache.fop.afp.modca.AbstractPageObject; import org.apache.fop.afp.modca.IncludeObject; @@ -165,39 +167,38 @@ public class AFPResourceManager { AFPResourceLevel resourceLevel = resourceInfo.getLevel(); ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel); + useInclude &= resourceGroup != null; if (useInclude) { + boolean usePageSegment = dataObjectInfo.isCreatePageSegment(); + + // if it is to reside within a resource group at print-file or external level + if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) { + if (usePageSegment) { + String pageSegmentName = "S10" + namedObj.getName().substring(3); + namedObj.setName(pageSegmentName); + PageSegment seg = new PageSegment(pageSegmentName); + seg.addObject(namedObj); + namedObj = seg; + } + + // wrap newly created data object in a resource object + namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType); + } - boolean usePageSegment = dataObjectInfo.isCreatePageSegment(); + // add data object into its resource group destination + resourceGroup.addObject(namedObj); - // if it is to reside within a resource group at print-file or external level - if (resourceLevel.isPrintFile() || resourceLevel.isExternal()) { + // create the include object + objectName = namedObj.getName(); if (usePageSegment) { - String pageSegmentName = "S10" + namedObj.getName().substring(3); - namedObj.setName(pageSegmentName); - PageSegment seg = new PageSegment(pageSegmentName); - seg.addObject(namedObj); - namedObj = seg; + includePageSegment(dataObjectInfo, objectName); + pageSegmentMap.put(resourceInfo, objectName); + } else { + includeObject(dataObjectInfo, objectName); + // record mapping of resource info to data object resource name + includeNameMap.put(resourceInfo, objectName); } - - // wrap newly created data object in a resource object - namedObj = dataObjectFactory.createResource(namedObj, resourceInfo, objectType); - } - - // add data object into its resource group destination - resourceGroup.addObject(namedObj); - - // create the include object - objectName = namedObj.getName(); - if (usePageSegment) { - includePageSegment(dataObjectInfo, objectName); - pageSegmentMap.put(resourceInfo, objectName); - } else { - includeObject(dataObjectInfo, objectName); - // record mapping of resource info to data object resource name - includeNameMap.put(resourceInfo, objectName); - } - } else { // not to be included so inline data object directly into the current page dataStream.getCurrentPage().addObject(namedObj); @@ -218,10 +219,32 @@ public class AFPResourceManager { private void includeObject(AFPDataObjectInfo dataObjectInfo, String objectName) { - IncludeObject includeObject - = dataObjectFactory.createInclude(objectName, dataObjectInfo); - dataStream.getCurrentPage().addObject(includeObject); + IncludeObject includeObject + = dataObjectFactory.createInclude(objectName, dataObjectInfo); + dataStream.getCurrentPage().addObject(includeObject); + } + + /** + * Handles font embedding. If a font is embeddable and has not already been embedded it will be. + * @param afpFont the AFP font to be checked for embedding + * @param charSet the associated character set + * @throws IOException if there's a problem while embedding the external resources + */ + public void embedFont(AFPFont afpFont, CharacterSet charSet) + throws IOException { + if (afpFont.isEmbeddable()) { + //Embed fonts (char sets and code pages) + if (charSet.getResourceAccessor() != null) { + ResourceAccessor accessor = charSet.getResourceAccessor(); + createIncludedResource( + charSet.getName(), accessor, + ResourceObject.TYPE_FONT_CHARACTER_SET); + createIncludedResource( + charSet.getCodePage(), accessor, + ResourceObject.TYPE_CODE_PAGE); + } } + } private void includePageSegment(AFPDataObjectInfo dataObjectInfo, String pageSegmentName) { @@ -241,7 +264,6 @@ public class AFPResourceManager { */ public void createIncludedResource(String resourceName, ResourceAccessor accessor, byte resourceObjectType) throws IOException { - AFPResourceLevel resourceLevel = new AFPResourceLevel(AFPResourceLevel.PRINT_FILE); URI uri; try { uri = new URI(resourceName.trim()); @@ -250,6 +272,21 @@ public class AFPResourceManager { + " (" + e.getMessage() + ")"); } + createIncludedResource(resourceName, uri, accessor, resourceObjectType); + } + + /** + * Creates an included resource object by loading the contained object from a file. + * @param resourceName the name of the resource + * @param uri the URI for the resource + * @param accessor resource accessor to access the resource with + * @param resourceObjectType the resource object type ({@link ResourceObject}.*) + * @throws IOException if an I/O error occurs while loading the resource + */ + public void createIncludedResource(String resourceName, URI uri, ResourceAccessor accessor, + byte resourceObjectType) throws IOException { + AFPResourceLevel resourceLevel = new AFPResourceLevel(AFPResourceLevel.PRINT_FILE); + AFPResourceInfo resourceInfo = new AFPResourceInfo(); resourceInfo.setLevel(resourceLevel); resourceInfo.setName(resourceName); diff --git a/src/java/org/apache/fop/afp/AbstractAFPPainter.java b/src/java/org/apache/fop/afp/AbstractAFPPainter.java index 576b8bb11..1358f8072 100644 --- a/src/java/org/apache/fop/afp/AbstractAFPPainter.java +++ b/src/java/org/apache/fop/afp/AbstractAFPPainter.java @@ -19,6 +19,8 @@ package org.apache.fop.afp; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -48,6 +50,7 @@ public abstract class AbstractAFPPainter { * Paints the painting item * * @param paintInfo the painting information + * @throws IOException if an I/O error occurs */ - public abstract void paint(PaintingInfo paintInfo); + public abstract void paint(PaintingInfo paintInfo) throws IOException; } diff --git a/src/java/org/apache/fop/afp/DataStream.java b/src/java/org/apache/fop/afp/DataStream.java index b1ff96859..cb68af94e 100644 --- a/src/java/org/apache/fop/afp/DataStream.java +++ b/src/java/org/apache/fop/afp/DataStream.java @@ -30,8 +30,9 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.afp.fonts.AFPFont; import org.apache.fop.afp.fonts.AFPFontAttributes; +import org.apache.fop.afp.fonts.AFPFont; +import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.AbstractPageObject; import org.apache.fop.afp.modca.Document; import org.apache.fop.afp.modca.InterchangeSet; @@ -41,6 +42,10 @@ import org.apache.fop.afp.modca.PageObject; import org.apache.fop.afp.modca.ResourceGroup; import org.apache.fop.afp.modca.TagLogicalElementBean; import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet; +import org.apache.fop.afp.ptoca.PtocaProducer; +import org.apache.fop.afp.ptoca.PtocaBuilder; +import org.apache.fop.util.CharUtilities; +import org.apache.fop.fonts.Font; /** * A data stream is a continuous ordered stream of data elements and objects @@ -347,11 +352,15 @@ public class DataStream { * Helper method to create text on the current page, this method delegates * to the current presentation text object in order to construct the text. * - * @param textDataInfo - * the afp text data + * @param textDataInfo the afp text data + * @param letterSpacing letter spacing to draw text with + * @param wordSpacing word Spacing to draw text with + * @param font is the font to draw text with + * @param charSet is the AFP Character Set to use with the text * @throws UnsupportedEncodingException thrown if character encoding is not supported */ - public void createText(AFPTextDataInfo textDataInfo) throws UnsupportedEncodingException { + public void createText(final AFPTextDataInfo textDataInfo, final int letterSpacing, final int wordSpacing, + final Font font, final CharacterSet charSet) throws UnsupportedEncodingException { int rotation = paintingState.getRotation(); if (rotation != 0) { textDataInfo.setRotation(rotation); @@ -359,7 +368,86 @@ public class DataStream { textDataInfo.setX(p.x); textDataInfo.setY(p.y); } - currentPage.createText(textDataInfo); + // use PtocaProducer to create PTX records + PtocaProducer producer = new PtocaProducer() { + + public void produce(PtocaBuilder builder) throws IOException { + builder.setTextOrientation(textDataInfo.getRotation()); + builder.absoluteMoveBaseline(textDataInfo.getY()); + builder.absoluteMoveInline(textDataInfo.getX()); + + builder.setExtendedTextColor(textDataInfo.getColor()); + builder.setCodedFont((byte)textDataInfo.getFontReference()); + + int l = textDataInfo.getString().length(); + StringBuffer sb = new StringBuffer(); + + int interCharacterAdjustment = 0; + AFPUnitConverter unitConv = paintingState.getUnitConverter(); + if (letterSpacing != 0) { + interCharacterAdjustment = Math.round(unitConv.mpt2units(letterSpacing)); + } + builder.setInterCharacterAdjustment(interCharacterAdjustment); + + int spaceWidth = font.getCharWidth(CharUtilities.SPACE); + int spacing = spaceWidth + letterSpacing; + int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(spacing)); + int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement; + if (wordSpacing != 0) { + varSpaceCharacterIncrement = Math.round(unitConv.mpt2units( + spaceWidth + wordSpacing + letterSpacing)); + } + builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement); + + boolean fixedSpaceMode = false; + + for (int i = 0; i < l; i++) { + char orgChar = textDataInfo.getString().charAt(i); + float glyphAdjust = 0; + if (CharUtilities.isFixedWidthSpace(orgChar)) { + flushText(builder, sb, charSet); + builder.setVariableSpaceCharacterIncrement( + fixedSpaceCharacterIncrement); + fixedSpaceMode = true; + sb.append(CharUtilities.SPACE); + int charWidth = font.getCharWidth(orgChar); + glyphAdjust += (charWidth - spaceWidth); + } else { + if (fixedSpaceMode) { + flushText(builder, sb, charSet); + builder.setVariableSpaceCharacterIncrement( + varSpaceCharacterIncrement); + fixedSpaceMode = false; + } + char ch; + if (orgChar == CharUtilities.NBSPACE) { + ch = ' '; //converted to normal space to allow word spacing + } else { + ch = orgChar; + } + sb.append(ch); + } + + if (glyphAdjust != 0) { + flushText(builder, sb, charSet); + int increment = Math.round(unitConv.mpt2units(glyphAdjust)); + builder.relativeMoveInline(increment); + } + } + flushText(builder, sb, charSet); + } + + private void flushText(PtocaBuilder builder, StringBuffer sb, + final CharacterSet charSet) throws IOException { + if (sb.length() > 0) { + builder.addTransparentData(charSet.encodeChars(sb)); + sb.setLength(0); + } + } + + }; + + currentPage.createText(producer); } /** diff --git a/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java b/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java index e607bef5f..417250df1 100644 --- a/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java +++ b/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java @@ -49,7 +49,7 @@ public class AFPBase12FontCollection implements FontCollection { private void addCharacterSet(RasterFont font, String charsetName, Base14Font base14) { for (int i = 0; i < RASTER_SIZES.length; i++) { - int size = RASTER_SIZES[i]; + int size = RASTER_SIZES[i] * 1000; FopCharacterSet characterSet = new FopCharacterSet( CharacterSet.DEFAULT_CODEPAGE, CharacterSet.DEFAULT_ENCODING, charsetName + CHARSET_REF[i], base14); @@ -80,22 +80,22 @@ public class AFPBase12FontCollection implements FontCollection { /** standard font family reference names for Helvetica font */ final String[] helveticaNames = {"Helvetica", "Arial", "sans-serif"}; - font = new RasterFont("Helvetica"); + font = createReferencedRasterFont("Helvetica"); addCharacterSet(font, "C0H200", new Helvetica()); num = addFontProperties(fontInfo, font, helveticaNames, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Helvetica Italic"); + font = createReferencedRasterFont("Helvetica Italic"); addCharacterSet(font, "C0H300", new HelveticaOblique()); num = addFontProperties(fontInfo, font, helveticaNames, Font.STYLE_ITALIC, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Helvetica (Semi) Bold"); + font = createReferencedRasterFont("Helvetica (Semi) Bold"); addCharacterSet(font, "C0H400", new HelveticaBold()); num = addFontProperties(fontInfo, font, helveticaNames, Font.STYLE_NORMAL, Font.WEIGHT_BOLD, num); - font = new RasterFont("Helvetica Italic (Semi) Bold"); + font = createReferencedRasterFont("Helvetica Italic (Semi) Bold"); addCharacterSet(font, "C0H500", new HelveticaOblique()); num = addFontProperties(fontInfo, font, helveticaNames, Font.STYLE_ITALIC, Font.WEIGHT_BOLD, num); @@ -107,22 +107,22 @@ public class AFPBase12FontCollection implements FontCollection { final String[] timesNames = {"Times", "TimesRoman", "Times Roman", "Times-Roman", "Times New Roman", "TimesNewRoman", "serif", "any"}; - font = new RasterFont("Times Roman"); + font = createReferencedRasterFont("Times Roman"); addCharacterSet(font, "CON200", new TimesRoman()); num = addFontProperties(fontInfo, font, timesNames, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Times Roman Italic"); + font = createReferencedRasterFont("Times Roman Italic"); addCharacterSet(font, "CON300", new TimesItalic()); num = addFontProperties(fontInfo, font, timesNames, Font.STYLE_ITALIC, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Times Roman Bold"); + font = createReferencedRasterFont("Times Roman Bold"); addCharacterSet(font, "CON400", new TimesBold()); num = addFontProperties(fontInfo, font, timesNames, Font.STYLE_NORMAL, Font.WEIGHT_BOLD, num); - font = new RasterFont("Times Roman Italic Bold"); + font = createReferencedRasterFont("Times Roman Italic Bold"); addCharacterSet(font, "CON500", new TimesBoldItalic()); num = addFontProperties(fontInfo, font, timesNames, Font.STYLE_ITALIC, Font.WEIGHT_BOLD, num); @@ -131,22 +131,22 @@ public class AFPBase12FontCollection implements FontCollection { /** standard font family reference names for Courier font */ final String[] courierNames = {"Courier", "monospace"}; - font = new RasterFont("Courier"); + font = createReferencedRasterFont("Courier"); addCharacterSet(font, "C04200", new Courier()); num = addFontProperties(fontInfo, font, courierNames, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Courier Italic"); + font = createReferencedRasterFont("Courier Italic"); addCharacterSet(font, "C04300", new CourierOblique()); num = addFontProperties(fontInfo, font, courierNames, Font.STYLE_ITALIC, Font.WEIGHT_NORMAL, num); - font = new RasterFont("Courier Bold"); + font = createReferencedRasterFont("Courier Bold"); addCharacterSet(font, "C04400", new CourierBold()); num = addFontProperties(fontInfo, font, courierNames, Font.STYLE_NORMAL, Font.WEIGHT_BOLD, num); - font = new RasterFont("Courier Italic Bold"); + font = createReferencedRasterFont("Courier Italic Bold"); addCharacterSet(font, "C04500", new CourierBoldOblique()); num = addFontProperties(fontInfo, font, courierNames, Font.STYLE_ITALIC, Font.WEIGHT_BOLD, num); @@ -154,4 +154,10 @@ public class AFPBase12FontCollection implements FontCollection { return num; } + private RasterFont createReferencedRasterFont(String fontFamily) { + RasterFont font = new RasterFont(fontFamily); + font.setEmbeddable(false); //Font is assumed to be available on the target platform + return font; + } + } diff --git a/src/java/org/apache/fop/afp/fonts/AFPFont.java b/src/java/org/apache/fop/afp/fonts/AFPFont.java index f56611087..a1c257d3e 100644 --- a/src/java/org/apache/fop/afp/fonts/AFPFont.java +++ b/src/java/org/apache/fop/afp/fonts/AFPFont.java @@ -36,6 +36,8 @@ public abstract class AFPFont extends Typeface { /** The font name */ protected String name; + private boolean embeddable = true; + /** * Constructor for the base font requires the name. * @param name the name of the font @@ -98,11 +100,19 @@ public abstract class AFPFont extends Typeface { public abstract CharacterSet getCharacterSet(int size); /** + * Controls whether this font is embeddable or not. + * @param value true to enable embedding, false otherwise. + */ + public void setEmbeddable(boolean value) { + this.embeddable = value; + } + + /** * Indicates if this font may be embedded. * @return True, if embedding is possible/permitted */ public boolean isEmbeddable() { - return false; //TODO Complete AFP font embedding + return this.embeddable; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/afp/fonts/AFPFontReader.java b/src/java/org/apache/fop/afp/fonts/AFPFontReader.java index 4e6a03259..25ea15278 100644 --- a/src/java/org/apache/fop/afp/fonts/AFPFontReader.java +++ b/src/java/org/apache/fop/afp/fonts/AFPFontReader.java @@ -177,7 +177,8 @@ public final class AFPFontReader { StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream); // Process D3A689 Font Descriptor - int pointSize = processFontDescriptor(structuredFieldReader); + FontDescriptor fontDescriptor = processFontDescriptor(structuredFieldReader); + characterSet.setNominalVerticalSize(fontDescriptor.getNominalFontSizeInMillipoints()); // Process D3A789 Font Control FontControl fontControl = processFontControl(structuredFieldReader); @@ -187,12 +188,13 @@ public final class AFPFontReader { CharacterSetOrientation[] characterSetOrientations = processFontOrientation(structuredFieldReader); - int dpi = fontControl.getDpi(); - int metricNormalizationFactor = 0; + int metricNormalizationFactor; if (fontControl.isRelative()) { metricNormalizationFactor = 1; } else { - metricNormalizationFactor = 72000 / dpi / pointSize; + int dpi = fontControl.getDpi(); + metricNormalizationFactor = 1000 * 72000 + / fontDescriptor.getNominalFontSizeInMillipoints() / dpi; } //process D3AC89 Font Position @@ -274,15 +276,13 @@ public final class AFPFontReader { * Process the font descriptor details using the structured field reader. * * @param structuredFieldReader the structured field reader - * @return the nominal size of the font (in points) + * @return a class representing the font descriptor */ - private static int processFontDescriptor(StructuredFieldReader structuredFieldReader) + private static FontDescriptor processFontDescriptor(StructuredFieldReader structuredFieldReader) throws IOException { byte[] fndData = structuredFieldReader.getNext(FONT_DESCRIPTOR_SF); - - int nominalPointSize = (((fndData[39] & 0xFF) << 8) + (fndData[40] & 0xFF)) / 10; - return nominalPointSize; + return new FontDescriptor(fndData); } /** @@ -303,8 +303,13 @@ public final class AFPFontReader { if (fncData[7] == (byte) 0x02) { fontControl.setRelative(true); } - int metricResolution = (((fncData[9] & 0xFF) << 8) + (fncData[10] & 0xFF)) / 10; - fontControl.setDpi(metricResolution); + int metricResolution = getUBIN(fncData, 9); + if (metricResolution == 1000) { + //Special case: 1000 units per em (rather than dpi) + fontControl.setUnitsPerEm(1000); + } else { + fontControl.setDpi(metricResolution / 10); + } } return fontControl; } @@ -378,7 +383,7 @@ public final class AFPFontReader { * font metric values */ private void processFontPosition(StructuredFieldReader structuredFieldReader, - CharacterSetOrientation[] characterSetOrientations, int metricNormalizationFactor) + CharacterSetOrientation[] characterSetOrientations, double metricNormalizationFactor) throws IOException { byte[] data = structuredFieldReader.getNext(FONT_POSITION_SF); @@ -397,17 +402,21 @@ public final class AFPFontReader { CharacterSetOrientation characterSetOrientation = characterSetOrientations[characterSetOrientationIndex]; - int xHeight = ((fpData[2] & 0xFF) << 8) + (fpData[3] & 0xFF); - int capHeight = ((fpData[4] & 0xFF) << 8) + (fpData[5] & 0xFF); - int ascHeight = ((fpData[6] & 0xFF) << 8) + (fpData[7] & 0xFF); - int dscHeight = ((fpData[8] & 0xFF) << 8) + (fpData[9] & 0xFF); + int xHeight = getSBIN(fpData, 2); + int capHeight = getSBIN(fpData, 4); + int ascHeight = getSBIN(fpData, 6); + int dscHeight = getSBIN(fpData, 8); dscHeight = dscHeight * -1; - characterSetOrientation.setXHeight(xHeight * metricNormalizationFactor); - characterSetOrientation.setCapHeight(capHeight * metricNormalizationFactor); - characterSetOrientation.setAscender(ascHeight * metricNormalizationFactor); - characterSetOrientation.setDescender(dscHeight * metricNormalizationFactor); + characterSetOrientation.setXHeight( + (int)Math.round(xHeight * metricNormalizationFactor)); + characterSetOrientation.setCapHeight( + (int)Math.round(capHeight * metricNormalizationFactor)); + characterSetOrientation.setAscender( + (int)Math.round(ascHeight * metricNormalizationFactor)); + characterSetOrientation.setDescender( + (int)Math.round(dscHeight * metricNormalizationFactor)); } } else if (position == 22) { position = 0; @@ -430,7 +439,8 @@ public final class AFPFontReader { * font metric values */ private void processFontIndex(StructuredFieldReader structuredFieldReader, - CharacterSetOrientation cso, Map/*<String,String>*/ codepage, int metricNormalizationFactor) + CharacterSetOrientation cso, Map/*<String,String>*/ codepage, + double metricNormalizationFactor) throws IOException { byte[] data = structuredFieldReader.getNext(FONT_INDEX_SF); @@ -442,6 +452,7 @@ public final class AFPFontReader { int lowest = 255; int highest = 0; + String firstABCMismatch = null; // Read data, ignoring bytes 0 - 2 for (int index = 3; index < data.length; index++) { @@ -464,7 +475,26 @@ public final class AFPFontReader { if (idx != null) { int cidx = idx.charAt(0); - int width = ((fiData[0] & 0xFF) << 8) + (fiData[1] & 0xFF); + int width = getUBIN(fiData, 0); + int a = getSBIN(fiData, 10); + int b = getUBIN(fiData, 12); + int c = getSBIN(fiData, 14); + int abc = a + b + c; + int diff = Math.abs(abc - width); + if (diff != 0 && width != 0) { + double diffPercent = 100 * diff / (double)width; + //if difference > 2% + if (diffPercent > 2) { + if (log.isTraceEnabled()) { + log.trace(gcgiString + ": " + + a + " + " + b + " + " + c + " = " + (a + b + c) + + " but found: " + width); + } + if (firstABCMismatch == null) { + firstABCMismatch = gcgiString; + } + } + } if (cidx < lowest) { lowest = cidx; @@ -474,9 +504,9 @@ public final class AFPFontReader { highest = cidx; } - int a = (width * metricNormalizationFactor); + int normalizedWidth = (int)Math.round(width * metricNormalizationFactor); - cso.setWidth(cidx, a); + cso.setWidth(cidx, normalizedWidth); } @@ -486,11 +516,32 @@ public final class AFPFontReader { cso.setFirstChar(lowest); cso.setLastChar(highest); + if (log.isDebugEnabled() && firstABCMismatch != null) { + //Debug level because it usually is no problem. + log.debug("Font has metrics inconsitencies where A+B+C doesn't equal the" + + " character increment. The first such character found: " + + firstABCMismatch); + } + } + + private static int getUBIN(byte[] data, int start) { + return ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF); + } + + private static int getSBIN(byte[] data, int start) { + int ubin = ((data[start] & 0xFF) << 8) + (data[start + 1] & 0xFF); + if ((ubin & 0x8000) != 0) { + //extend sign + return ubin | 0xFFFF0000; + } else { + return ubin; + } } private class FontControl { private int dpi; + private int unitsPerEm; private boolean isRelative = false; @@ -502,6 +553,14 @@ public final class AFPFontReader { dpi = i; } + public int getUnitsPerEm() { + return this.unitsPerEm; + } + + public void setUnitsPerEm(int value) { + this.unitsPerEm = value; + } + public boolean isRelative() { return isRelative; } @@ -511,4 +570,18 @@ public final class AFPFontReader { } } + private static class FontDescriptor { + + private byte[] data; + + public FontDescriptor(byte[] data) { + this.data = data; + } + + public int getNominalFontSizeInMillipoints() { + int nominalFontSize = 100 * getUBIN(data, 39); + return nominalFontSize; + } + } + } diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSet.java b/src/java/org/apache/fop/afp/fonts/CharacterSet.java index 9573506b3..48d5f4f30 100644 --- a/src/java/org/apache/fop/afp/fonts/CharacterSet.java +++ b/src/java/org/apache/fop/afp/fonts/CharacterSet.java @@ -95,6 +95,9 @@ public class CharacterSet { /** The collection of objects for each orientation */ private Map characterSetOrientations = null; + /** The nominal vertical size (in millipoints) for bitmap fonts. 0 for outline fonts. */ + private int nominalVerticalSize = 0; + /** * Constructor for the CharacterSetMetric object, the character set is used * to load the font information from the actual AFP font. @@ -159,6 +162,23 @@ public class CharacterSet { } /** + * Sets the nominal vertical size of the font in the case of bitmap fonts. + * @param nominalVerticalSize the nominal vertical size (in millipoints) + */ + public void setNominalVerticalSize(int nominalVerticalSize) { + this.nominalVerticalSize = nominalVerticalSize; + } + + /** + * Returns the nominal vertical size of the font in the case of bitmap fonts. For outline fonts, + * zero is returned, because these are scalable fonts. + * @return the nominal vertical size (in millipoints) for bitmap fonts, or 0 for outline fonts. + */ + public int getNominalVerticalSize() { + return this.nominalVerticalSize; + } + + /** * Ascender height is the distance from the character baseline to the * top of the character box. A negative ascender height signifies that * all of the graphic character is below the character baseline. For diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java index 8ced8e356..1946fd4a4 100644 --- a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java +++ b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java @@ -191,7 +191,7 @@ public class CharacterSetOrientation { * a character rotation other than 0, ascender height loses its * meaning when the character is lying on its side or is upside down * with respect to normal viewing orientation. For the general case, - * Ascender Height is the character�s most positive y-axis value. + * Ascender Height is the character's most positive y-axis value. * For bounded character boxes, for a given character having an * ascender, ascender height and baseline offset are equal. * @param ascender the ascender to set diff --git a/src/java/org/apache/fop/afp/fonts/OutlineFont.java b/src/java/org/apache/fop/afp/fonts/OutlineFont.java index b97d5f3ab..8dca69f9c 100644 --- a/src/java/org/apache/fop/afp/fonts/OutlineFont.java +++ b/src/java/org/apache/fop/afp/fonts/OutlineFont.java @@ -87,8 +87,7 @@ public class OutlineFont extends AFPFont { * "x-height" (the height of the letter "x"), such as "d", "t", or "h". Also * used to denote the part of the letter extending above the x-height. * - * @param size - * the point size + * @param size the font size (in mpt) * @return the ascender for the given size */ public int getAscender(int size) { @@ -98,8 +97,7 @@ public class OutlineFont extends AFPFont { /** * Obtains the height of capital letters for the specified point size. * - * @param size - * the point size + * @param size the font size (in mpt) * @return the cap height for the given size */ public int getCapHeight(int size) { @@ -111,8 +109,7 @@ public class OutlineFont extends AFPFont { * base line, such as "g", "j", or "p". Also used to denote the part of the * letter extending below the base line. * - * @param size - * the point size + * @param size the font size (in mpt) * @return the descender for the given size */ public int getDescender(int size) { @@ -122,8 +119,7 @@ public class OutlineFont extends AFPFont { /** * The "x-height" (the height of the letter "x"). * - * @param size - * the point size + * @param size the font size (in mpt) * @return the x height for the given size */ public int getXHeight(int size) { @@ -133,7 +129,7 @@ public class OutlineFont extends AFPFont { /** * Obtain the width of the character for the specified point size. * @param character the character - * @param size point size + * @param size the font size (in mpt) * @return the width of the character for the specified point size */ public int getWidth(int character, int size) { @@ -144,8 +140,7 @@ public class OutlineFont extends AFPFont { * Get the getWidth (in 1/1000ths of a point size) of all characters in this * character set. * - * @param size - * the point size + * @param size the font size (in mpt) * @return the widths of all characters */ public int[] getWidths(int size) { diff --git a/src/java/org/apache/fop/afp/fonts/RasterFont.java b/src/java/org/apache/fop/afp/fonts/RasterFont.java index 6288dadbb..115773214 100644 --- a/src/java/org/apache/fop/afp/fonts/RasterFont.java +++ b/src/java/org/apache/fop/afp/fonts/RasterFont.java @@ -19,8 +19,10 @@ package org.apache.fop.afp.fonts; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.SortedMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -37,8 +39,9 @@ public class RasterFont extends AFPFont { /** Static logging instance */ protected static final Log log = LogFactory.getLog("org.apache.fop.afp.fonts"); - private final Map/*<String,CharacterSet>*/ charSets - = new java.util.HashMap/*<String,CharacterSet>*/(); + private final SortedMap/*<Integer,CharacterSet>*/ charSets + = new java.util.TreeMap/*<Integer,CharacterSet>*/(); + private Map/*<Integer,CharacterSet>*/ substitutionCharSets; private CharacterSet charSet = null; @@ -55,58 +58,77 @@ public class RasterFont extends AFPFont { /** * Adds the character set for the given point size - * @param size point size + * @param size point size (in mpt) * @param characterSet character set */ public void addCharacterSet(int size, CharacterSet characterSet) { - this.charSets.put(String.valueOf(size), characterSet); + //TODO: replace with Integer.valueOf() once we switch to Java 5 + this.charSets.put(new Integer(size), characterSet); this.charSet = characterSet; } - /** Describes the unit millipoint. */ - public static final String MPT = "mpt"; - /** * Get the character set metrics for the specified point size. * - * @param size the point size + * @param size the point size (in mpt) * @return the character set metrics */ public CharacterSet getCharacterSet(int size) { - String pointsize = String.valueOf(size / 1000); - CharacterSet csm = (CharacterSet) charSets.get(pointsize); - if (csm == null) { - csm = (CharacterSet) charSets.get(size + MPT); + //TODO: replace with Integer.valueOf() once we switch to Java 5 + Integer requestedSize = new Integer(size); + CharacterSet csm = (CharacterSet) charSets.get(requestedSize); + + if (csm != null) { + return csm; } - if (csm == null) { - // Get char set with nearest font size - int distance = Integer.MAX_VALUE; - for (Iterator it = charSets.entrySet().iterator(); it.hasNext();) { - Map.Entry me = (Map.Entry)it.next(); - String key = (String)me.getKey(); - if (!key.endsWith(MPT)) { - int mpt = Integer.parseInt(key) * 1000; - if (Math.abs(size - mpt) < distance) { - distance = Math.abs(size - mpt); - pointsize = (String)me.getKey(); - csm = (CharacterSet)me.getValue(); - } - } + + if (substitutionCharSets != null) { + //Check first if a substitution has already been added + csm = (CharacterSet) substitutionCharSets.get(requestedSize); + } + + if (csm == null && !charSets.isEmpty()) { + // No match or substitution found, but there exist entries + // for other sizes + // Get char set with nearest, smallest font size + SortedMap smallerSizes = charSets.headMap(requestedSize); + SortedMap largerSizes = charSets.tailMap(requestedSize); + int smallerSize = smallerSizes.isEmpty() ? 0 + : ((Integer)smallerSizes.lastKey()).intValue(); + int largerSize = largerSizes.isEmpty() ? Integer.MAX_VALUE + : ((Integer)largerSizes.firstKey()).intValue(); + + Integer fontSize; + if (!smallerSizes.isEmpty() + && (size - smallerSize) <= (largerSize - size)) { + fontSize = new Integer(smallerSize); + } else { + fontSize = new Integer(largerSize); } + csm = (CharacterSet) charSets.get(fontSize); + if (csm != null) { - charSets.put(size + MPT, csm); - String msg = "No " + (size / 1000) + "pt font " + getFontName() - + " found, substituted with " + pointsize + "pt font"; + // Add the substitute mapping, so subsequent calls will + // find it immediately + if (substitutionCharSets == null) { + substitutionCharSets = new HashMap(); + } + substitutionCharSets.put(requestedSize, csm); + String msg = "No " + (size / 1000f) + "pt font " + getFontName() + + " found, substituted with " + fontSize.intValue() / 1000f + "pt font"; log.warn(msg); } } + if (csm == null) { + // Still no match -> error String msg = "No font found for font " + getFontName() - + " with point size " + pointsize; + + " with point size " + size / 1000f; log.error(msg); throw new FontRuntimeException(msg); } + return csm; } @@ -145,26 +167,37 @@ public class RasterFont extends AFPFont { } + private int metricsToAbsoluteSize(CharacterSet cs, int value, int givenSize) { + int nominalVerticalSize = cs.getNominalVerticalSize(); + if (nominalVerticalSize != 0) { + return value * nominalVerticalSize; + } else { + return value * givenSize; + } + } + /** * The ascender is the part of a lowercase letter that extends above the * "x-height" (the height of the letter "x"), such as "d", "t", or "h". Also * used to denote the part of the letter extending above the x-height. * - * @param size the point size + * @param size the font size (in mpt) * @return the ascender for the given point size */ public int getAscender(int size) { - return getCharacterSet(size).getAscender() * size; + CharacterSet cs = getCharacterSet(size); + return metricsToAbsoluteSize(cs, cs.getAscender(), size); } /** * Obtains the height of capital letters for the specified point size. * - * @param size the point size + * @param size the font size (in mpt) * @return the cap height for the specified point size */ public int getCapHeight(int size) { - return getCharacterSet(size).getCapHeight() * size; + CharacterSet cs = getCharacterSet(size); + return metricsToAbsoluteSize(cs, cs.getCapHeight(), size); } /** @@ -172,43 +205,50 @@ public class RasterFont extends AFPFont { * base line, such as "g", "j", or "p". Also used to denote the part of the * letter extending below the base line. * - * @param size the point size + * @param size the font size (in mpt) * @return the descender for the specified point size */ public int getDescender(int size) { - return getCharacterSet(size).getDescender() * size; + CharacterSet cs = getCharacterSet(size); + return metricsToAbsoluteSize(cs, cs.getDescender(), size); } /** * The "x-height" (the height of the letter "x"). * - * @param size the point size + * @param size the font size (in mpt) * @return the x height for the given point size */ public int getXHeight(int size) { - return getCharacterSet(size).getXHeight() * size; + CharacterSet cs = getCharacterSet(size); + return metricsToAbsoluteSize(cs, cs.getXHeight(), size); } /** * Obtain the width of the character for the specified point size. * @param character the character - * @param size the point size + * @param size the font size (in mpt) * @return the width for the given point size */ public int getWidth(int character, int size) { - return getCharacterSet(size).getWidth(character) * size; + CharacterSet cs = getCharacterSet(size); + return metricsToAbsoluteSize(cs, cs.getWidth(character), size); } /** * Get the getWidth (in 1/1000ths of a point size) of all characters in this * character set. * - * @param size - * the point size + * @param size the font size (in mpt) * @return the widths of all characters */ public int[] getWidths(int size) { - return getCharacterSet(size).getWidths(); + CharacterSet cs = getCharacterSet(size); + int[] widths = cs.getWidths(); + for (int i = 0, c = widths.length; i < c; i++) { + widths[i] = metricsToAbsoluteSize(cs, widths[i], size); + } + return widths; } /** @@ -239,5 +279,4 @@ public class RasterFont extends AFPFont { public String getEncodingName() { return charSet.getEncoding(); } - } diff --git a/src/java/org/apache/fop/afp/goca/AbstractGraphicsCoord.java b/src/java/org/apache/fop/afp/goca/AbstractGraphicsCoord.java index 3d8495667..3c3442def 100644 --- a/src/java/org/apache/fop/afp/goca/AbstractGraphicsCoord.java +++ b/src/java/org/apache/fop/afp/goca/AbstractGraphicsCoord.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/AbstractGraphicsDrawingOrderContainer.java b/src/java/org/apache/fop/afp/goca/AbstractGraphicsDrawingOrderContainer.java index 34398b094..dbeaa26d0 100644 --- a/src/java/org/apache/fop/afp/goca/AbstractGraphicsDrawingOrderContainer.java +++ b/src/java/org/apache/fop/afp/goca/AbstractGraphicsDrawingOrderContainer.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; @@ -46,6 +46,8 @@ implements StructuredData, Completable, Startable { /** object has started */ private boolean started = false; + private int dataLength = 0; + /** * Default constructor */ @@ -78,6 +80,7 @@ implements StructuredData, Completable, Startable { */ public void addObject(StructuredData object) { objects.add(object); + dataLength += object.getDataLength(); } /** @@ -88,6 +91,7 @@ implements StructuredData, Completable, Startable { public void addAll(AbstractGraphicsDrawingOrderContainer graphicsContainer) { Collection/*<StructuredDataObject>*/ objects = graphicsContainer.getObjects(); objects.addAll(objects); + dataLength += graphicsContainer.getDataLength(); } /** @@ -107,9 +111,11 @@ implements StructuredData, Completable, Startable { public StructuredData removeLast() { int lastIndex = objects.size() - 1; StructuredData object = null; - if (lastIndex > -1) { - object = (StructuredData)objects.get(lastIndex); - objects.remove(lastIndex); + if (lastIndex >= 0) { + object = (StructuredData)objects.remove(lastIndex); + } + if (object != null) { + dataLength -= object.getDataLength(); } return object; } @@ -121,12 +127,7 @@ implements StructuredData, Completable, Startable { * all enclosed objects (and their containers) */ public int getDataLength() { - int dataLen = 0; - Iterator it = objects.iterator(); - while (it.hasNext()) { - dataLen += ((StructuredData)it.next()).getDataLength(); - } - return dataLen; + return this.dataLength; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/afp/goca/GraphicsBox.java b/src/java/org/apache/fop/afp/goca/GraphicsBox.java index 945697ec2..97c08b9ee 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsBox.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsBox.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsChainedSegment.java b/src/java/org/apache/fop/afp/goca/GraphicsChainedSegment.java index 8a92db296..1162e83aa 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsChainedSegment.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsChainedSegment.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java b/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java index 70039d167..4094314a2 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsCharacterString.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsData.java b/src/java/org/apache/fop/afp/goca/GraphicsData.java index c75057dc5..1ba757e4b 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsData.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsData.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsFillet.java b/src/java/org/apache/fop/afp/goca/GraphicsFillet.java index 294be6d9b..9dad2fe1c 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsFillet.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsFillet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsFullArc.java b/src/java/org/apache/fop/afp/goca/GraphicsFullArc.java index a4b6916ae..47bf53079 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsFullArc.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsFullArc.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsLine.java b/src/java/org/apache/fop/afp/goca/GraphicsLine.java index 17bd43ce0..dea03960a 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsLine.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsLine.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetArcParameters.java b/src/java/org/apache/fop/afp/goca/GraphicsSetArcParameters.java index 693cf21a9..6b3dc98ec 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetArcParameters.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetArcParameters.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetCharacterSet.java b/src/java/org/apache/fop/afp/goca/GraphicsSetCharacterSet.java index b3d1158fe..f2a450516 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetCharacterSet.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetCharacterSet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetCurrentPosition.java b/src/java/org/apache/fop/afp/goca/GraphicsSetCurrentPosition.java index 675c2f034..1335f2473 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetCurrentPosition.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetCurrentPosition.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetLineType.java b/src/java/org/apache/fop/afp/goca/GraphicsSetLineType.java index b6512f57c..b4224b3c9 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetLineType.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetLineType.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetLineWidth.java b/src/java/org/apache/fop/afp/goca/GraphicsSetLineWidth.java index 96eac0677..4ba208bb0 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetLineWidth.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetLineWidth.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetPatternSymbol.java b/src/java/org/apache/fop/afp/goca/GraphicsSetPatternSymbol.java index 3d6cf7cd6..e2cc081ce 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetPatternSymbol.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetPatternSymbol.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/goca/GraphicsSetProcessColor.java b/src/java/org/apache/fop/afp/goca/GraphicsSetProcessColor.java index 05a6ee5d1..f0c4aa321 100644 --- a/src/java/org/apache/fop/afp/goca/GraphicsSetProcessColor.java +++ b/src/java/org/apache/fop/afp/goca/GraphicsSetProcessColor.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.goca; diff --git a/src/java/org/apache/fop/afp/modca/AbstractDataObject.java b/src/java/org/apache/fop/afp/modca/AbstractDataObject.java index fcab3cb4a..d76de9259 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractDataObject.java +++ b/src/java/org/apache/fop/afp/modca/AbstractDataObject.java @@ -34,7 +34,8 @@ import org.apache.fop.afp.Startable; * Abstract base class used by the ImageObject and GraphicsObject which both * have define an ObjectEnvironmentGroup */ -public abstract class AbstractDataObject extends AbstractNamedAFPObject implements Startable, Completable { +public abstract class AbstractDataObject extends AbstractNamedAFPObject + implements Startable, Completable { /** the object environment group */ protected ObjectEnvironmentGroup objectEnvironmentGroup = null; @@ -81,14 +82,14 @@ public abstract class AbstractDataObject extends AbstractNamedAFPObject implemen AFPResourceInfo resourceInfo = dataObjectInfo.getResourceInfo(); AFPResourceLevel resourceLevel = resourceInfo.getLevel(); ObjectAreaPosition objectAreaPosition = null; + int rotation = objectAreaInfo.getRotation(); if (resourceLevel.isInline()) { int x = objectAreaInfo.getX(); int y = objectAreaInfo.getY(); - int rotation = objectAreaInfo.getRotation(); objectAreaPosition = factory.createObjectAreaPosition(x, y, rotation); } else { // positional values are specified in the oaOffset of the include object - objectAreaPosition = factory.createObjectAreaPosition(0, 0, 0); + objectAreaPosition = factory.createObjectAreaPosition(0, 0, rotation); } objectAreaPosition.setReferenceCoordinateSystem( ObjectAreaPosition.REFCSYS_PAGE_SEGMENT_RELATIVE); diff --git a/src/java/org/apache/fop/afp/modca/AbstractEnvironmentGroup.java b/src/java/org/apache/fop/afp/modca/AbstractEnvironmentGroup.java index 4ba9abff8..abc3aea87 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractEnvironmentGroup.java +++ b/src/java/org/apache/fop/afp/modca/AbstractEnvironmentGroup.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/AbstractPageObject.java b/src/java/org/apache/fop/afp/modca/AbstractPageObject.java index c043faf2e..af676410f 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractPageObject.java +++ b/src/java/org/apache/fop/afp/modca/AbstractPageObject.java @@ -25,9 +25,9 @@ import java.io.UnsupportedEncodingException; import java.util.List; import org.apache.fop.afp.AFPLineDataInfo; -import org.apache.fop.afp.AFPTextDataInfo; import org.apache.fop.afp.Completable; import org.apache.fop.afp.Factory; +import org.apache.fop.afp.ptoca.PtocaProducer; import org.apache.fop.afp.fonts.AFPFont; /** @@ -170,8 +170,10 @@ public abstract class AbstractPageObject extends AbstractNamedAFPObject implemen * the afp text data * @throws UnsupportedEncodingException thrown if character encoding is not supported */ - public void createText(AFPTextDataInfo textDataInfo) throws UnsupportedEncodingException { - getPresentationTextObject().createTextData(textDataInfo); + public void createText(PtocaProducer producer) throws UnsupportedEncodingException { + //getPresentationTextObject().createTextData(textDataInfo); + getPresentationTextObject().createControlSequences(producer); + } /** @@ -212,6 +214,17 @@ public abstract class AbstractPageObject extends AbstractNamedAFPObject implemen } /** + * Returns the list of {@link TagLogicalElement}s. + * @return the TLEs + */ + protected List getTagLogicalElements() { + if (tagLogicalElements == null) { + this.tagLogicalElements = new java.util.ArrayList/*<TagLogicalElement>*/(); + } + return this.tagLogicalElements; + } + + /** * Creates a TagLogicalElement on the page. * * @param name @@ -223,10 +236,8 @@ public abstract class AbstractPageObject extends AbstractNamedAFPObject implemen */ public void createTagLogicalElement(String name, String value, int tleID) { TagLogicalElement tle = new TagLogicalElement(name, value, tleID); - if (tagLogicalElements == null) { - tagLogicalElements = new java.util.ArrayList/*<TagLogicalElement>*/(); - } - tagLogicalElements.add(tle); + List list = getTagLogicalElements(); + list.add(tle); } /** diff --git a/src/java/org/apache/fop/afp/modca/AbstractResourceEnvironmentGroupContainer.java b/src/java/org/apache/fop/afp/modca/AbstractResourceEnvironmentGroupContainer.java index baba170f7..1319b3cdb 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractResourceEnvironmentGroupContainer.java +++ b/src/java/org/apache/fop/afp/modca/AbstractResourceEnvironmentGroupContainer.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/AbstractResourceGroupContainer.java b/src/java/org/apache/fop/afp/modca/AbstractResourceGroupContainer.java index 6546fa978..2c5e02328 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractResourceGroupContainer.java +++ b/src/java/org/apache/fop/afp/modca/AbstractResourceGroupContainer.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; @@ -141,12 +141,25 @@ implements Streamable { /** {@inheritDoc} */ protected void writeObjects(Collection/*<AbstractAFPObject>*/ objects, OutputStream os) - throws IOException { + throws IOException { + writeObjects(objects, os, false); + } + + /** + * Writes a collection of {@link AbstractAFPObject}s to the AFP Datastream. + * + * @param objects a list of AFPObjects + * @param os The stream to write to + * @param forceWrite true if writing should happen in any case + * @throws java.io.IOException an I/O exception of some sort has occurred. + */ + protected void writeObjects(Collection/*<AbstractAFPObject>*/ objects, OutputStream os, + boolean forceWrite) throws IOException { if (objects != null && objects.size() > 0) { Iterator it = objects.iterator(); while (it.hasNext()) { AbstractAFPObject ao = (AbstractAFPObject)it.next(); - if (canWrite(ao)) { + if (forceWrite || canWrite(ao)) { ao.writeToStream(os); it.remove(); } else { diff --git a/src/java/org/apache/fop/afp/modca/ImageObject.java b/src/java/org/apache/fop/afp/modca/ImageObject.java index bbbc25bea..65802f6ca 100644 --- a/src/java/org/apache/fop/afp/modca/ImageObject.java +++ b/src/java/org/apache/fop/afp/modca/ImageObject.java @@ -28,7 +28,6 @@ import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPImageObjectInfo; import org.apache.fop.afp.Factory; import org.apache.fop.afp.ioca.ImageSegment; -import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; /** * An IOCA Image Data Object @@ -66,10 +65,6 @@ public class ImageObject extends AbstractDataObject { int dataWidth = imageObjectInfo.getDataWidth(); int dataHeight = imageObjectInfo.getDataHeight(); -// AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo(); -// int widthRes = objectAreaInfo.getWidthRes(); -// int heightRes = objectAreaInfo.getHeightRes(); - int dataWidthRes = imageObjectInfo.getDataWidthRes(); int dataHeightRes = imageObjectInfo.getDataWidthRes(); ImageDataDescriptor imageDataDescriptor @@ -79,7 +74,7 @@ public class ImageObject extends AbstractDataObject { } getObjectEnvironmentGroup().setDataDescriptor(imageDataDescriptor); getObjectEnvironmentGroup().setMapImageObject( - new MapImageObject(MappingOptionTriplet.SCALE_TO_FILL)); + new MapImageObject(dataObjectInfo.getMappingOption())); getImageSegment().setImageSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); } diff --git a/src/java/org/apache/fop/afp/modca/InterchangeSet.java b/src/java/org/apache/fop/afp/modca/InterchangeSet.java index 28a4da42b..f4b020239 100644 --- a/src/java/org/apache/fop/afp/modca/InterchangeSet.java +++ b/src/java/org/apache/fop/afp/modca/InterchangeSet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/MapCodedFont.java b/src/java/org/apache/fop/afp/modca/MapCodedFont.java index 54b4d1796..e732a8bb7 100644 --- a/src/java/org/apache/fop/afp/modca/MapCodedFont.java +++ b/src/java/org/apache/fop/afp/modca/MapCodedFont.java @@ -206,7 +206,7 @@ public class MapCodedFont extends AbstractStructuredObject { // There are approximately 72 points to 1 inch or 20 1440ths per point. - fontDefinition.scale = ((size / 1000) * 20); + fontDefinition.scale = 20 * size / 1000; fontDefinition.codePage = cs.getCodePage().getBytes( AFPConstants.EBCIDIC_ENCODING); diff --git a/src/java/org/apache/fop/afp/modca/MapDataResource.java b/src/java/org/apache/fop/afp/modca/MapDataResource.java index 566f60ce5..0bac920bd 100644 --- a/src/java/org/apache/fop/afp/modca/MapDataResource.java +++ b/src/java/org/apache/fop/afp/modca/MapDataResource.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/ObjectContainer.java b/src/java/org/apache/fop/afp/modca/ObjectContainer.java index 39b935d01..e5a57ebe9 100644 --- a/src/java/org/apache/fop/afp/modca/ObjectContainer.java +++ b/src/java/org/apache/fop/afp/modca/ObjectContainer.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/PageGroup.java b/src/java/org/apache/fop/afp/modca/PageGroup.java index 4e578718b..f70b6fc52 100644 --- a/src/java/org/apache/fop/afp/modca/PageGroup.java +++ b/src/java/org/apache/fop/afp/modca/PageGroup.java @@ -21,7 +21,6 @@ package org.apache.fop.afp.modca; import java.io.IOException; import java.io.OutputStream; -import java.util.List; import org.apache.fop.afp.Factory; @@ -36,9 +35,6 @@ import org.apache.fop.afp.Factory; */ public class PageGroup extends AbstractResourceEnvironmentGroupContainer { - /** The tag logical elements contained within this group */ - private List tagLogicalElements = null; - /** * Sequence number for TLE's. */ @@ -56,13 +52,6 @@ public class PageGroup extends AbstractResourceEnvironmentGroupContainer { this.tleSequence = tleSequence; } - private List getTagLogicalElements() { - if (tagLogicalElements == null) { - this.tagLogicalElements = new java.util.ArrayList(); - } - return this.tagLogicalElements; - } - /** * Creates a TagLogicalElement on the page. * @@ -88,7 +77,7 @@ public class PageGroup extends AbstractResourceEnvironmentGroupContainer { /** {@inheritDoc} */ protected void writeContent(OutputStream os) throws IOException { - writeObjects(tagLogicalElements, os); + writeObjects(tagLogicalElements, os, true); super.writeContent(os); } diff --git a/src/java/org/apache/fop/afp/modca/PageSegment.java b/src/java/org/apache/fop/afp/modca/PageSegment.java index ab1388efb..b765d6c2f 100644 --- a/src/java/org/apache/fop/afp/modca/PageSegment.java +++ b/src/java/org/apache/fop/afp/modca/PageSegment.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/PreprocessPresentationObject.java b/src/java/org/apache/fop/afp/modca/PreprocessPresentationObject.java index 72e261662..ff16e89b6 100644 --- a/src/java/org/apache/fop/afp/modca/PreprocessPresentationObject.java +++ b/src/java/org/apache/fop/afp/modca/PreprocessPresentationObject.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/Registry.java b/src/java/org/apache/fop/afp/modca/Registry.java index 481a72afd..eade967ec 100644 --- a/src/java/org/apache/fop/afp/modca/Registry.java +++ b/src/java/org/apache/fop/afp/modca/Registry.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/ResourceEnvironmentGroup.java b/src/java/org/apache/fop/afp/modca/ResourceEnvironmentGroup.java index 9a898ef4d..3f9258741 100644 --- a/src/java/org/apache/fop/afp/modca/ResourceEnvironmentGroup.java +++ b/src/java/org/apache/fop/afp/modca/ResourceEnvironmentGroup.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/ResourceObject.java b/src/java/org/apache/fop/afp/modca/ResourceObject.java index 0f555a42e..6f97bed93 100644 --- a/src/java/org/apache/fop/afp/modca/ResourceObject.java +++ b/src/java/org/apache/fop/afp/modca/ResourceObject.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca; diff --git a/src/java/org/apache/fop/afp/modca/TagLogicalElement.java b/src/java/org/apache/fop/afp/modca/TagLogicalElement.java index 9ccd58bfb..5c1f7bbbb 100644 --- a/src/java/org/apache/fop/afp/modca/TagLogicalElement.java +++ b/src/java/org/apache/fop/afp/modca/TagLogicalElement.java @@ -64,7 +64,7 @@ public class TagLogicalElement extends AbstractAFPObject { /** * Construct a tag logical element with the name and value specified. - * + * * @param name the name of the tag logical element * @param value the value of the tag logical element * @param tleID unique identifier for TLE within AFP stream @@ -135,7 +135,7 @@ public class TagLogicalElement extends AbstractAFPObject { data[pos++] = tleByteValue[i]; } // attribute qualifier - data[pos++] = 0x10; + data[pos++] = 0x0A; data[pos++] = (byte)0x80; byte[] id = BinaryUtils.convert(tleID, 4); for (int i = 0; i < id.length; i++) { diff --git a/src/java/org/apache/fop/afp/modca/triplets/AbstractTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/AbstractTriplet.java index 4e75d4204..598df1b98 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/AbstractTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/AbstractTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/modca/triplets/FullyQualifiedNameTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/FullyQualifiedNameTriplet.java index 55653457c..65c438199 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/FullyQualifiedNameTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/FullyQualifiedNameTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java index 0d20d0227..2f19eca83 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java @@ -50,9 +50,14 @@ public class MappingOptionTriplet extends AbstractTriplet { */ public static final byte CENTER_AND_TRIM = 0x30; -// public static final byte MIGRATION_MAPPING_1 = 0x41; -// public static final byte MIGRATION_MAPPING_2 = 0x42; -// public static final byte MIGRATION_MAPPING_3 = 0x50; + /** Migration mapping option: Image point-to-pel. */ + public static final byte IMAGE_POINT_TO_PEL = 0x41; + + /** Migration mapping option: Image point-to-pel with double dot. */ + public static final byte IMAGE_POINT_TO_PEL_DOUBLE_DOT = 0x42; + + /** Migration mapping option: Replicate and trim. */ + public static final byte REPLICATE_AND_TRIM = 0x50; /** the data object is centred, aspect ratio is not always preserved */ public static final byte SCALE_TO_FILL = 0x60; diff --git a/src/java/org/apache/fop/afp/modca/triplets/MeasurementUnitsTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/MeasurementUnitsTriplet.java index 68d3fc40c..b04c6d43c 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/MeasurementUnitsTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/MeasurementUnitsTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/modca/triplets/ObjectAreaSizeTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/ObjectAreaSizeTriplet.java index 3d408639e..1b1aa5b88 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/ObjectAreaSizeTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/ObjectAreaSizeTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/modca/triplets/ObjectClassificationTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/ObjectClassificationTriplet.java index 9c2ab7bc4..8430a47ee 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/ObjectClassificationTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/ObjectClassificationTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/modca/triplets/ResourceObjectTypeTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/ResourceObjectTypeTriplet.java index e4b13177d..a6d83f83b 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/ResourceObjectTypeTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/ResourceObjectTypeTriplet.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.afp.modca.triplets; diff --git a/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java index 3a6507252..40adb5ed8 100644 --- a/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java +++ b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java @@ -20,6 +20,7 @@ package org.apache.fop.afp.ptoca; import java.awt.Color; +import java.awt.color.ColorSpace; import java.io.IOException; import java.io.OutputStream; @@ -314,19 +315,38 @@ public abstract class PtocaBuilder implements PtocaConstants { return; } newControlSequence(); - writeByte(0x00); // Reserved; must be zero - writeByte(0x01); // Color space - 0x01 = RGB - writeByte(0x00); // Reserved; must be zero - writeByte(0x00); // Reserved; must be zero - writeByte(0x00); // Reserved; must be zero - writeByte(0x00); // Reserved; must be zero - writeByte(8); // Number of bits in component 1 - writeByte(8); // Number of bits in component 2 - writeByte(8); // Number of bits in component 3 - writeByte(0); // Number of bits in component 4 - writeByte(col.getRed()); // Red intensity - writeByte(col.getGreen()); // Green intensity - writeByte(col.getBlue()); // Blue intensity + if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) { + writeByte(0x00); // Reserved; must be zero + writeByte(0x04); // Color space - 0x04 = CMYK + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(8); // Number of bits in component 1 + writeByte(8); // Number of bits in component 2 + writeByte(8); // Number of bits in component 3 + writeByte(8); // Number of bits in component 4 + float[] comps = col.getColorComponents(null); + assert comps.length == 4; + for (int i = 0; i < 4; i++) { + int component = Math.round(comps[i] * 256); + writeByte(component); + } + } else { + writeByte(0x00); // Reserved; must be zero + writeByte(0x01); // Color space - 0x01 = RGB + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(0x00); // Reserved; must be zero + writeByte(8); // Number of bits in component 1 + writeByte(8); // Number of bits in component 2 + writeByte(8); // Number of bits in component 3 + writeByte(0); // Number of bits in component 4 + writeByte(col.getRed()); // Red intensity + writeByte(col.getGreen()); // Green intensity + writeByte(col.getBlue()); // Blue intensity + } commit(chained(SEC)); this.currentColor = col; } diff --git a/src/java/org/apache/fop/afp/util/CubicBezierApproximator.java b/src/java/org/apache/fop/afp/util/CubicBezierApproximator.java new file mode 100644 index 000000000..d3ed41c76 --- /dev/null +++ b/src/java/org/apache/fop/afp/util/CubicBezierApproximator.java @@ -0,0 +1,126 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.afp.util; + +import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Double; + +/** + * This class can be used to convert a cubic bezier curve within + * a path into multiple quadratic bezier curves which will approximate + * the original cubic curve. + * The various techniques are described here: + * http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm + */ +public class CubicBezierApproximator { + + /** + * This method will take in an array containing the x and y coordinates of the four control + * points that describe the cubic bezier curve to be approximated using the fixed mid point + * approximation. The curve will be approximated using four quadratic bezier curves the points + * for which will be returned in a two dimensional array, with each array within that containing + * the points for a single quadratic curve. The returned data will not include the start point + * for any of the curves; the first point passed in to this method should already have been + * set as the current position and will be the assumed start of the first curve. + * + * @param cubicControlPointCoords an array containing the x and y coordinates of the + * four control points. + * @return an array of arrays containing the x and y coordinates of the quadratic curves + * that approximate the original supplied cubic bezier curve. + */ + public static double[][] fixedMidPointApproximation(double[] cubicControlPointCoords) { + if (cubicControlPointCoords.length < 8) { + throw new IllegalArgumentException("Must have at least 8 coordinates"); + } + + //extract point objects from source array + Point2D p0 = new Point2D.Double(cubicControlPointCoords[0], cubicControlPointCoords[1]); + Point2D p1 = new Point2D.Double(cubicControlPointCoords[2], cubicControlPointCoords[3]); + Point2D p2 = new Point2D.Double(cubicControlPointCoords[4], cubicControlPointCoords[5]); + Point2D p3 = new Point2D.Double(cubicControlPointCoords[6], cubicControlPointCoords[7]); + + //calculates the useful base points + Point2D pa = getPointOnSegment(p0, p1, 3.0 / 4.0); + Point2D pb = getPointOnSegment(p3, p2, 3.0 / 4.0); + + //get 1/16 of the [P3, P0] segment + double dx = (p3.getX() - p0.getX()) / 16.0; + double dy = (p3.getY() - p0.getY()) / 16.0; + + //calculates control point 1 + Point2D pc1 = getPointOnSegment(p0, p1, 3.0 / 8.0); + + //calculates control point 2 + Point2D pc2 = getPointOnSegment(pa, pb, 3.0 / 8.0); + pc2 = movePoint(pc2, -dx, -dy); + + //calculates control point 3 + Point2D pc3 = getPointOnSegment(pb, pa, 3.0 / 8.0); + pc3 = movePoint(pc3, dx, dy); + + //calculates control point 4 + Point2D pc4 = getPointOnSegment(p3, p2, 3.0 / 8.0); + + //calculates the 3 anchor points + Point2D pa1 = getMidPoint(pc1, pc2); + Point2D pa2 = getMidPoint(pa, pb); + Point2D pa3 = getMidPoint(pc3, pc4); + + //return the points for the four quadratic curves + return new double[][] { + {pc1.getX(), pc1.getY(), pa1.getX(), pa1.getY()}, + {pc2.getX(), pc2.getY(), pa2.getX(), pa2.getY()}, + {pc3.getX(), pc3.getY(), pa3.getX(), pa3.getY()}, + {pc4.getX(), pc4.getY(), p3.getX(), p3.getY()}}; + } + + private static Double movePoint(Point2D point, double dx, double dy) { + return new Point2D.Double(point.getX() + dx, point.getY() + dy); + } + + /** + * This method will calculate the coordinates of a point half way along a segment [P0, P1] + * + * @param p0 - The point describing the start of the segment. + * @param p1 - The point describing the end of the segment. + * @return a Point object describing the coordinates of the calculated point on the segment. + */ + private static Point2D getMidPoint(Point2D p0, Point2D p1) { + return getPointOnSegment(p0, p1, 0.5); + } + + /** + * This method will calculate the coordinates of a point on a segment [P0, P1] + * whose distance along the segment [P0, P1] from P0, is the given ratio + * of the length the [P0, P1] segment. + * + * @param p0 The point describing the start of the segment. + * @param p1 The point describing the end of the segment. + * @param ratio The distance of the point being calculated from P0 as a ratio of + * the segment length. + * @return a Point object describing the coordinates of the calculated point on the segment. + */ + private static Point2D getPointOnSegment(Point2D p0, Point2D p1, double ratio) { + double x = p0.getX() + ((p1.getX() - p0.getX()) * ratio); + double y = p0.getY() + ((p1.getY() - p0.getY()) * ratio); + return new Point2D.Double(x, y); + } + +} diff --git a/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java b/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java index 97646542b..053db01af 100644 --- a/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java +++ b/src/java/org/apache/fop/afp/util/DefaultFOPResourceAccessor.java @@ -63,8 +63,10 @@ public class DefaultFOPResourceAccessor extends SimpleResourceAccessor { URI resolved = resolveAgainstBase(uri); //Step 2: resolve against the user agent --> stream - Source src; - src = userAgent.resolveURI(resolved.toASCIIString(), this.categoryBaseURI); + String base = (this.categoryBaseURI != null + ? this.categoryBaseURI + : this.userAgent.getBaseURL()); + Source src = userAgent.resolveURI(resolved.toASCIIString(), base); if (src == null) { throw new FileNotFoundException("Resource not found: " + uri.toASCIIString()); diff --git a/src/java/org/apache/fop/apps/FOURIResolver.java b/src/java/org/apache/fop/apps/FOURIResolver.java index f96711d31..c7d564ea3 100644 --- a/src/java/org/apache/fop/apps/FOURIResolver.java +++ b/src/java/org/apache/fop/apps/FOURIResolver.java @@ -68,6 +68,8 @@ public class FOURIResolver implements javax.xml.transform.URIResolver { * @throws MalformedURLException if there's a problem with a file URL */ public String checkBaseURL(String base) throws MalformedURLException { + // replace back slash with forward slash to ensure windows file:/// URLS are supported + base = base.replace('\\', '/'); if (!base.endsWith("/")) { // The behavior described by RFC 3986 regarding resolution of relative // references may be misleading for normal users: diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index d2af24a01..25d110086 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -20,6 +20,7 @@ package org.apache.fop.area; import java.awt.Color; +import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.io.FileNotFoundException; import java.io.IOException; @@ -38,9 +39,10 @@ import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; - import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; @@ -48,9 +50,6 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.DefaultHandler; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; @@ -404,7 +403,7 @@ public class AreaTreeParser { if (currentPageViewport != null) { throw new IllegalStateException("currentPageViewport must be null"); } - Rectangle2D viewArea = XMLUtil.getAttributeAsRectangle2D(attributes, "bounds"); + Rectangle viewArea = XMLUtil.getAttributeAsRectangle(attributes, "bounds"); int pageNumber = XMLUtil.getAttributeAsInt(attributes, "nr", -1); String key = attributes.getValue("key"); String pageNumberString = attributes.getValue("formatted-nr"); diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java index 321ca4c03..63740386e 100644 --- a/src/java/org/apache/fop/area/PageViewport.java +++ b/src/java/org/apache/fop/area/PageViewport.java @@ -20,7 +20,6 @@ package org.apache.fop.area; import java.awt.Rectangle; -import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -48,7 +47,7 @@ import org.apache.fop.fo.pagination.SimplePageMaster; public class PageViewport extends AreaTreeObject implements Resolvable, Cloneable { private Page page; - private Rectangle2D viewArea; + private Rectangle viewArea; private String simplePageMasterName; /** @@ -100,6 +99,7 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl public PageViewport(SimplePageMaster spm, int pageNumber, String pageStr, boolean blank) { this.simplePageMasterName = spm.getMasterName(); setExtensionAttachments(spm.getExtensionAttachments()); + setForeignAttributes(spm.getForeignAttributes()); this.blank = blank; int pageWidth = spm.getPageWidth().getValue(); int pageHeight = spm.getPageHeight().getValue(); @@ -118,11 +118,14 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl if (original.extensionAttachments != null) { setExtensionAttachments(original.extensionAttachments); } + if (original.foreignAttributes != null) { + setForeignAttributes(original.foreignAttributes); + } this.pageIndex = original.pageIndex; this.pageNumber = original.pageNumber; this.pageNumberString = original.pageNumberString; this.page = (Page)original.page.clone(); - this.viewArea = (Rectangle2D)original.viewArea.clone(); + this.viewArea = new Rectangle(original.viewArea); this.simplePageMasterName = original.simplePageMasterName; this.blank = original.blank; } @@ -135,7 +138,7 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl * @param simplePageMasterName name of the original simple-page-master that generated this page * @param blank true if this is a blank page */ - public PageViewport(Rectangle2D viewArea, int pageNumber, String pageStr, + public PageViewport(Rectangle viewArea, int pageNumber, String pageStr, String simplePageMasterName, boolean blank) { this.viewArea = viewArea; this.pageNumber = pageNumber; @@ -161,7 +164,7 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl * Get the view area rectangle of this viewport. * @return the rectangle for this viewport */ - public Rectangle2D getViewArea() { + public Rectangle getViewArea() { return viewArea; } diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java index e82ce13a7..ece99d1de 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -30,10 +30,9 @@ import java.util.Vector; import javax.swing.UIManager; -import org.xml.sax.SAXException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.xml.sax.SAXException; import org.apache.fop.Version; import org.apache.fop.accessibility.AccessibilityUtil; @@ -336,8 +335,8 @@ public class CommandLineOptions { } else if (args[i].equals("-a")) { this.renderingOptions.put(AccessibilityUtil.ACCESSIBILITY, Boolean.TRUE); } else if (args[i].equals("-v")) { + /* Currently just print the version */ printVersion(); - return false; } else if (args[i].equals("-param")) { if (i + 2 < args.length) { String name = args[++i]; @@ -360,6 +359,9 @@ public class CommandLineOptions { getPDFEncryptionParams().setAllowEditContent(false); } else if (args[i].equals("-noannotations")) { getPDFEncryptionParams().setAllowEditAnnotations(false); + } else if (args[i].equals("-version")) { + printVersion(); + return false; } else if (!isOption(args[i])) { i = i + parseUnknownOption(args, i); } else { @@ -1122,6 +1124,7 @@ public class CommandLineOptions { "\nUSAGE\nfop [options] [-fo|-xml] infile [-xsl file] " + "[-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl|-ps|-txt|-at [mime]|-print] <outfile>\n" + " [OPTIONS] \n" + + " -version print FOP version and exit\n" + " -d debug mode \n" + " -x dump configuration settings \n" + " -q quiet mode \n" @@ -1130,7 +1133,7 @@ public class CommandLineOptions { + " -r relaxed/less strict validation (where available)\n" + " -dpi xxx target resolution in dots per inch (dpi) where xxx is a number\n" + " -s for area tree XML, down to block areas only\n" - + " -v to show FOP version being used\n\n" + + " -v run in verbose mode (currently simply print FOP version and continue)\n\n" + " -o [password] PDF file will be encrypted with option owner password\n" + " -u [password] PDF file will be encrypted with option user password\n" + " -noprint PDF file will be encrypted without printing permission\n" @@ -1140,8 +1143,8 @@ public class CommandLineOptions { + " -a enables accessibility features (Tagged PDF etc., default off)\n" + " -pdfprofile prof PDF file will be generated with the specified profile\n" + " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n" - + " -conserve Enable memory-conservation policy (trades memory-consumption for disk I/O)" - + " (Note: currently only influences whether the area tree is serialized.)" + + " -conserve Enable memory-conservation policy (trades memory-consumption for disk I/O)\n" + + " (Note: currently only influences whether the area tree is serialized.)\n\n" + " [INPUT] \n" + " infile xsl:fo input file (the same as the next) \n" + " (use '-' for infile to pipe input from stdin)\n" diff --git a/src/java/org/apache/fop/events/EventFormatter.xml b/src/java/org/apache/fop/events/EventFormatter.xml index d26fbeb93..147744a0d 100644 --- a/src/java/org/apache/fop/events/EventFormatter.xml +++ b/src/java/org/apache/fop/events/EventFormatter.xml @@ -1,4 +1,5 @@ -<?xml version="1.0" encoding="UTF-8"?><!-- +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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. @@ -13,7 +14,9 @@ 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. ---><!-- $Id$ --><catalogue xml:lang="en"> +--> +<!-- $Id$ --> +<catalogue xml:lang="en"> <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> <message key="rule.markerDescendantOfFlow">An fo:marker is permitted only as the descendant of an fo:flow.</message> <message key="rule.retrieveMarkerDescendantOfStaticContent">An fo:retrieve-marker is permitted only as the descendant of an fo:static-content.</message> @@ -102,7 +105,4 @@ Any reference to it will be considered a reference to the first occurrence in th <message key="org.apache.fop.fonts.FontEventAdapter.fontSubstituted">Font "{requested}" not found. Substituting with "{effective}".</message> <message key="org.apache.fop.fonts.FontEventAdapter.fontLoadingErrorAtAutoDetection">Unable to load font file: {fontURL}.[ Reason: {e}]</message> <message key="org.apache.fop.fonts.FontEventAdapter.glyphNotAvailable">Glyph "{ch}" (0x{ch,hex}[, {ch,glyph-name}]) not available in font "{fontName}".</message> -<message key="org.apache.fop.afp.AFPEventProducer.warnDefaultFontSetup"/> -<message key="org.apache.fop.afp.AFPEventProducer.warnMissingDefaultFont"/> -<message key="org.apache.fop.afp.AFPEventProducer.characterSetEncodingError"/> </catalogue> diff --git a/src/java/org/apache/fop/events/EventFormatter_de.xml b/src/java/org/apache/fop/events/EventFormatter_de.xml index c65d24f73..0e2bd5c44 100644 --- a/src/java/org/apache/fop/events/EventFormatter_de.xml +++ b/src/java/org/apache/fop/events/EventFormatter_de.xml @@ -1,23 +1,23 @@ -<?xml version="1.0" encoding="UTF-8"?>
-<!--
- 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.
--->
-<!-- $Id$ -->
-<catalogue xml:lang="de">
- <message key="locator">[ (Siehe Position {loc})| (Siehe {#gatherContextInfo})| (Keine Kontextinformationen verfügbar)]</message>
- <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">In "{elementName}" darf nur ein einziges "{offendingNode}" vorkommen!{{locator}}</message>
- <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Dem Element "{elementName}" fehlt ein verlangtes Property "{propertyName}"!{{locator}}</message>
-</catalogue>
+<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<!-- $Id$ --> +<catalogue xml:lang="de"> + <message key="locator">[ (Siehe Position {loc})| (Siehe {#gatherContextInfo})| (Keine Kontextinformationen verfügbar)]</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.tooManyNodes">In "{elementName}" darf nur ein einziges "{offendingNode}" vorkommen!{{locator}}</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.missingProperty">Dem Element "{elementName}" fehlt ein verlangtes Property "{propertyName}"!{{locator}}</message> +</catalogue> diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java index 8c4e3416d..67f5d1d30 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -23,13 +23,12 @@ package org.apache.fop.fo; import java.util.ListIterator; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.helpers.LocatorImpl; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.apache.xmlgraphics.util.QName; import org.apache.fop.apps.FOPException; @@ -91,23 +90,19 @@ public abstract class FONode implements Cloneable { */ public FONode clone(FONode cloneparent, boolean removeChildren) throws FOPException { - try { - FONode foNode = (FONode) clone(); - foNode.parent = cloneparent; - foNode.siblings = null; - return foNode; - } catch (CloneNotSupportedException cnse) { - return null; - } + FONode foNode = (FONode) clone(); + foNode.parent = cloneparent; + foNode.siblings = null; + return foNode; } - /** - * Perform a shallow cloning operation - * - * {@inheritDoc} - */ - protected Object clone() throws CloneNotSupportedException { - return super.clone(); + /** {@inheritDoc} */ + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // Can't happen + } } /** diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index 7c77f0697..c848eb4f1 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -288,6 +288,9 @@ public class FOTreeBuilder extends DefaultHandler { builderContext.switchMarkerContext(true); } } + if (foNode.getNameId() == Constants.FO_PAGE_SEQUENCE) { + builderContext.getXMLWhiteSpaceHandler().reset(); + } } catch (IllegalArgumentException e) { throw new SAXException(e); } diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index 55844e6db..2ebfaaf95 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -53,7 +53,7 @@ public abstract class FObj extends FONode implements Constants { protected FONode firstChild; /** The list of extension attachments, null if none */ - private List extensionAttachments = null; + private List/*<ExtensionAttachment>*/ extensionAttachments = null; /** The map of foreign attributes, null if none */ private Map foreignAttributes = null; @@ -554,7 +554,7 @@ public abstract class FObj extends FONode implements Constants { "Parameter attachment must not be null"); } if (extensionAttachments == null) { - extensionAttachments = new java.util.ArrayList(); + extensionAttachments = new java.util.ArrayList/*<ExtensionAttachment>*/(); } if (log.isDebugEnabled()) { log.debug("ExtensionAttachment of category " @@ -565,7 +565,7 @@ public abstract class FObj extends FONode implements Constants { } /** @return the extension attachments of this FObj. */ - public List getExtensionAttachments() { + public List/*<ExtensionAttachment>*/ getExtensionAttachments() { if (extensionAttachments == null) { return Collections.EMPTY_LIST; } else { diff --git a/src/java/org/apache/fop/fo/XMLWhiteSpaceHandler.java b/src/java/org/apache/fop/fo/XMLWhiteSpaceHandler.java index cad9fb729..51e84551c 100644 --- a/src/java/org/apache/fop/fo/XMLWhiteSpaceHandler.java +++ b/src/java/org/apache/fop/fo/XMLWhiteSpaceHandler.java @@ -234,6 +234,18 @@ public class XMLWhiteSpaceHandler { } /** + * Reset the handler, release all references + */ + protected final void reset() { + if (pendingInlines != null) { + pendingInlines.clear(); + } + nestedBlockStack.clear(); + charIter = null; + firstWhiteSpaceInSeq = null; + } + + /** * Handle white-space for the fo that is passed in, starting at * firstTextNode (when a nested FO is encountered) * @param fo the FO for which to handle white-space diff --git a/src/java/org/apache/fop/fo/expr/ICCColorFunction.java b/src/java/org/apache/fop/fo/expr/ICCColorFunction.java index 2069945f1..9e2ef2e7d 100644 --- a/src/java/org/apache/fop/fo/expr/ICCColorFunction.java +++ b/src/java/org/apache/fop/fo/expr/ICCColorFunction.java @@ -24,6 +24,7 @@ import org.apache.fop.fo.pagination.ColorProfile; import org.apache.fop.fo.pagination.Declarations; import org.apache.fop.fo.properties.ColorProperty; import org.apache.fop.fo.properties.Property; +import org.apache.fop.util.ColorUtil; /** * Implements the rgb-icc() function. @@ -63,13 +64,15 @@ class ICCColorFunction extends FunctionBase { } else { cp = decls.getColorProfile(colorProfileName); if (cp == null) { - PropertyException pe = new PropertyException("The " + colorProfileName - + " color profile was not declared"); - pe.setPropertyInfo(pInfo); - throw pe; + if (!ColorUtil.isPseudoProfile(colorProfileName)) { + PropertyException pe = new PropertyException("The " + colorProfileName + + " color profile was not declared"); + pe.setPropertyInfo(pInfo); + throw pe; + } } } - String src = cp.getSrc(); + String src = (cp != null ? cp.getSrc() : ""); float red = 0, green = 0, blue = 0; red = args[0].getNumber().floatValue(); diff --git a/src/java/org/apache/fop/fo/expr/PropertyParser.java b/src/java/org/apache/fop/fo/expr/PropertyParser.java index 7ae1db7f3..87f640651 100644 --- a/src/java/org/apache/fop/fo/expr/PropertyParser.java +++ b/src/java/org/apache/fop/fo/expr/PropertyParser.java @@ -19,6 +19,12 @@ package org.apache.fop.fo.expr; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import org.apache.xmlgraphics.util.UnitConv; + import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.Numeric; @@ -31,10 +37,6 @@ import org.apache.fop.fo.properties.PercentLength; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.StringProperty; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - /** * Class to parse XSL-FO property expressions. * This class is heavily based on the epxression parser in James Clark's @@ -99,7 +101,7 @@ public final class PropertyParser extends PropertyTokenizer { /** * Private constructor. Called by the static parse() method. * @param propExpr The specified value (attribute on the xml element). - * @param propInfo A PropertyInfo object representing the context in + * @param pInfo A PropertyInfo object representing the context in * which the property expression is to be evaluated. */ private PropertyParser(String propExpr, PropertyInfo pInfo) { @@ -310,12 +312,13 @@ public final class PropertyParser extends PropertyTokenizer { propInfo.currentFontSize()); } else { if ("px".equals(unitPart)) { - //pass the ratio between source-resolution and + //pass the ratio between target-resolution and //the default resolution of 72dpi + float resolution = propInfo.getPropertyList().getFObj() + .getUserAgent().getSourceResolution(); prop = FixedLength.getInstance( numPart, unitPart, - propInfo.getPropertyList().getFObj() - .getUserAgent().getSourceResolution() / 72.0f); + UnitConv.IN2PT / resolution); } else { //use default resolution of 72dpi prop = FixedLength.getInstance(numPart, unitPart); diff --git a/src/java/org/apache/fop/fo/expr/PropertyTokenizer.java b/src/java/org/apache/fop/fo/expr/PropertyTokenizer.java index 2ddcd0922..5baa0c4d8 100644 --- a/src/java/org/apache/fop/fo/expr/PropertyTokenizer.java +++ b/src/java/org/apache/fop/fo/expr/PropertyTokenizer.java @@ -243,15 +243,20 @@ class PropertyTokenizer { } - private void nextColor () throws PropertyException { + private void nextColor() throws PropertyException { if (exprIndex < exprLength && isHexDigit(expr.charAt(exprIndex))) { ++exprIndex; scanHexDigits(); - currentToken = TOK_COLORSPEC; + int len = exprIndex - currentTokenStartIndex - 1; + if (len % 3 == 0) { + currentToken = TOK_COLORSPEC; + } else { + scanRestOfName(); + currentToken = TOK_NCNAME; + } currentTokenValue = expr.substring(currentTokenStartIndex, exprIndex); - // Probably should have some multiple of 3 for length! return; } else { throw new PropertyException("illegal character '#'"); @@ -263,11 +268,15 @@ class PropertyTokenizer { */ private void scanName() { if (exprIndex < exprLength && isNameStartChar(expr.charAt(exprIndex))) { - while (++exprIndex < exprLength - && isNameChar(expr.charAt(exprIndex))) { } + scanRestOfName(); } } + private void scanRestOfName() { + while (++exprIndex < exprLength + && isNameChar(expr.charAt(exprIndex))) { } + } + /** * Attempt to recognize a valid sequence of decimal DIGITS in the * input expression. diff --git a/src/java/org/apache/fop/fo/flow/AbstractRetrieveMarker.java b/src/java/org/apache/fop/fo/flow/AbstractRetrieveMarker.java index 1432c9381..51ae7441d 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractRetrieveMarker.java +++ b/src/java/org/apache/fop/fo/flow/AbstractRetrieveMarker.java @@ -155,11 +155,6 @@ public abstract class AbstractRetrieveMarker extends FObjMixed { private void cloneFromMarker(Marker marker) throws FOPException { - // clean up remnants from a possible earlier layout - if (firstChild != null) { - currentTextNode = null; - firstChild = null; - } cloneSubtree(marker.getChildNodes(), this, marker, propertyList); handleWhiteSpaceFor(this, null); @@ -171,6 +166,11 @@ public abstract class AbstractRetrieveMarker extends FObjMixed { * @param marker the marker that is to be cloned */ public void bindMarker(Marker marker) { + // clean up remnants from a possible earlier layout + if (firstChild != null) { + currentTextNode = null; + firstChild = null; + } if (marker.getChildNodes() != null) { try { cloneFromMarker(marker); diff --git a/src/java/org/apache/fop/fo/flow/table/EffRow.java b/src/java/org/apache/fop/fo/flow/table/EffRow.java index 16d507303..a5853cd91 100644 --- a/src/java/org/apache/fop/fo/flow/table/EffRow.java +++ b/src/java/org/apache/fop/fo/flow/table/EffRow.java @@ -23,8 +23,7 @@ import java.util.Iterator; import java.util.List; import org.apache.fop.fo.Constants; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.table.TableRowIterator; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.BreakUtil; @@ -170,20 +169,19 @@ public class EffRow { * * @return the strength of the keep-with-previous constraint */ - public int getKeepWithPreviousStrength() { - int strength = BlockLevelLayoutManager.KEEP_AUTO; + public Keep getKeepWithPrevious() { + Keep keep = Keep.KEEP_AUTO; TableRow row = getTableRow(); if (row != null) { - strength = Math.max(strength, - KeepUtil.getCombinedBlockLevelKeepStrength(row.getKeepWithPrevious())); + keep = Keep.getKeep(row.getKeepWithPrevious()); } for (Iterator iter = gridUnits.iterator(); iter.hasNext();) { GridUnit gu = (GridUnit) iter.next(); if (gu.isPrimary()) { - strength = Math.max(strength, gu.getPrimary().getKeepWithPreviousStrength()); + keep = keep.compare(gu.getPrimary().getKeepWithPrevious()); } } - return strength; + return keep; } /** @@ -192,20 +190,19 @@ public class EffRow { * * @return the strength of the keep-with-next constraint */ - public int getKeepWithNextStrength() { - int strength = BlockLevelLayoutManager.KEEP_AUTO; + public Keep getKeepWithNext() { + Keep keep = Keep.KEEP_AUTO; TableRow row = getTableRow(); if (row != null) { - strength = Math.max(strength, - KeepUtil.getCombinedBlockLevelKeepStrength(row.getKeepWithNext())); + keep = Keep.getKeep(row.getKeepWithNext()); } for (Iterator iter = gridUnits.iterator(); iter.hasNext();) { GridUnit gu = (GridUnit) iter.next(); if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) { - strength = Math.max(strength, gu.getPrimary().getKeepWithNextStrength()); + keep = keep.compare(gu.getPrimary().getKeepWithNext()); } } - return strength; + return keep; } /** @@ -213,16 +210,13 @@ public class EffRow { * not take the parent table's keeps into account! * @return the keep-together strength */ - public int getKeepTogetherStrength() { + public Keep getKeepTogether() { TableRow row = getTableRow(); - int strength = BlockLevelLayoutManager.KEEP_AUTO; + Keep keep = Keep.KEEP_AUTO; if (row != null) { - strength = Math.max(strength, KeepUtil.getKeepStrength( - row.getKeepTogether().getWithinPage())); - strength = Math.max(strength, KeepUtil.getKeepStrength( - row.getKeepTogether().getWithinColumn())); + keep = Keep.getKeep(row.getKeepTogether()); } - return strength; + return keep; } /** diff --git a/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java b/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java index 3254e928b..9326d6cd4 100644 --- a/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java +++ b/src/java/org/apache/fop/fo/flow/table/PrimaryGridUnit.java @@ -19,14 +19,13 @@ package org.apache.fop.fo.flow.table; -import java.util.LinkedList; import java.util.List; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.table.TableCellLayoutManager; /** @@ -54,8 +53,8 @@ public class PrimaryGridUnit extends GridUnit { private boolean isSeparateBorderModel; private int halfBorderSeparationBPD; - private int keepWithPrevious = BlockLevelLayoutManager.KEEP_AUTO; - private int keepWithNext = BlockLevelLayoutManager.KEEP_AUTO; + private Keep keepWithPrevious = Keep.KEEP_AUTO; + private Keep keepWithNext = Keep.KEEP_AUTO; private int breakBefore = Constants.EN_AUTO; private int breakAfter = Constants.EN_AUTO; @@ -334,16 +333,16 @@ public class PrimaryGridUnit extends GridUnit { * * @return the keep-with-previous strength */ - public int getKeepWithPreviousStrength() { + public Keep getKeepWithPrevious() { return keepWithPrevious; } /** * Don't use, reserved for TableCellLM. TODO - * @param strength the keep strength + * @param keep the keep strength */ - public void setKeepWithPreviousStrength(int strength) { - this.keepWithPrevious = strength; + public void setKeepWithPrevious(Keep keep) { + this.keepWithPrevious = keep; } /** @@ -352,16 +351,16 @@ public class PrimaryGridUnit extends GridUnit { * * @return the keep-with-next strength */ - public int getKeepWithNextStrength() { + public Keep getKeepWithNext() { return keepWithNext; } /** * Don't use, reserved for TableCellLM. TODO - * @param strength the keep strength + * @param keep the keep strength */ - public void setKeepWithNextStrength(int strength) { - this.keepWithNext = strength; + public void setKeepWithNext(Keep keep) { + this.keepWithNext = keep; } /** diff --git a/src/java/org/apache/fop/fo/flow/table/TableBody.java b/src/java/org/apache/fop/fo/flow/table/TableBody.java index 0ddfa97e3..0b42fd837 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableBody.java +++ b/src/java/org/apache/fop/fo/flow/table/TableBody.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.fo.flow.table; diff --git a/src/java/org/apache/fop/fo/flow/table/TablePart.java b/src/java/org/apache/fop/fo/flow/table/TablePart.java index b1db59d91..5b04cddc7 100644 --- a/src/java/org/apache/fop/fo/flow/table/TablePart.java +++ b/src/java/org/apache/fop/fo/flow/table/TablePart.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: TableBody.java 655614 2008-05-12 19:37:39Z vhennebert $ */ +/* $Id$ */ package org.apache.fop.fo.flow.table; @@ -70,6 +70,13 @@ public abstract class TablePart extends TableCellContainer { } /** {@inheritDoc} */ + protected Object clone() { + TablePart clone = (TablePart) super.clone(); + clone.rowGroups = new LinkedList(rowGroups); + return clone; + } + + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { commonBorderPaddingBackground = pList.getBorderPaddingBackgroundProps(); super.bind(pList); @@ -103,13 +110,13 @@ public abstract class TablePart extends TableCellContainer { pendingSpans = null; columnNumberManager = null; } - if (!(tableRowsFound || tableCellsFound)) { missingChildElementError("marker* (table-row+|table-cell+)", true); getParent().removeChild(this); } else { finishLastRowGroup(); } + } /** {@inheritDoc} */ @@ -197,6 +204,9 @@ public abstract class TablePart extends TableCellContainer { //nop } } + //TODO: possible performance problems in case of large tables... + //If the number of children grows significantly large, the default + //implementation in FObj will get slower and slower... super.addChildNode(child); } diff --git a/src/java/org/apache/fop/fo/properties/LengthProperty.java b/src/java/org/apache/fop/fo/properties/LengthProperty.java index 4ffe38074..3f569054e 100644 --- a/src/java/org/apache/fop/fo/properties/LengthProperty.java +++ b/src/java/org/apache/fop/fo/properties/LengthProperty.java @@ -19,6 +19,8 @@ package org.apache.fop.fo.properties; +import org.apache.xmlgraphics.util.UnitConv; + import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.FObj; @@ -57,9 +59,10 @@ public abstract class LengthProperty extends Property } if (p instanceof NumberProperty) { //Assume pixels (like in HTML) when there's no unit + float resolution = propertyList.getFObj().getUserAgent().getSourceResolution(); return FixedLength.getInstance( p.getNumeric().getNumericValue(), "px", - propertyList.getFObj().getUserAgent().getSourceResolution() / 72.0f); + UnitConv.IN2PT / resolution); } Length val = p.getLength(); if (val != null) { diff --git a/src/java/org/apache/fop/fonts/FontAdder.java b/src/java/org/apache/fop/fonts/FontAdder.java index 0d6a730cf..f0e511c42 100644 --- a/src/java/org/apache/fop/fonts/FontAdder.java +++ b/src/java/org/apache/fop/fonts/FontAdder.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.fonts; diff --git a/src/java/org/apache/fop/fonts/FontDetector.java b/src/java/org/apache/fop/fonts/FontDetector.java index 09671f1f8..828cad2b5 100644 --- a/src/java/org/apache/fop/fonts/FontDetector.java +++ b/src/java/org/apache/fop/fonts/FontDetector.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.fonts; diff --git a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java b/src/java/org/apache/fop/fonts/FontInfoConfigurator.java index c97901163..208c32803 100644 --- a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java +++ b/src/java/org/apache/fop/fonts/FontInfoConfigurator.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.fonts; diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java index 8739b42a4..fb4725bd4 100644 --- a/src/java/org/apache/fop/fonts/SingleByteFont.java +++ b/src/java/org/apache/fop/fonts/SingleByteFont.java @@ -94,7 +94,7 @@ public class SingleByteFont extends CustomFont { /** {@inheritDoc} */ public int[] getWidths() { int[] arr = new int[width.length]; - System.arraycopy(width, 0, arr, 0, width.length - 1); + System.arraycopy(width, 0, arr, 0, width.length); return arr; } diff --git a/src/java/org/apache/fop/fonts/apps/PFMReader.java b/src/java/org/apache/fop/fonts/apps/PFMReader.java index 90dd1fd28..e5e8ca524 100644 --- a/src/java/org/apache/fop/fonts/apps/PFMReader.java +++ b/src/java/org/apache/fop/fonts/apps/PFMReader.java @@ -171,7 +171,7 @@ public class PFMReader extends AbstractFontReader { * @param pfm The PFM file to preview. */ public void preview(PFMFile pfm) { - if (log != null & log.isInfoEnabled()) { + if (log != null && log.isInfoEnabled()) { log.info("Font: " + pfm.getWindowsName()); log.info("Name: " + pfm.getPostscriptName()); log.info("CharSet: " + pfm.getCharSetName()); @@ -219,14 +219,15 @@ public class PFMReader extends AbstractFontReader { root.appendChild(el); el.appendChild(doc.createTextNode(pfm.getPostscriptName())); - String s = pfm.getPostscriptName(); - int pos = s.indexOf("-"); - if (pos >= 0) { - char[] sb = new char[s.length() - 1]; - s.getChars(0, pos, sb, 0); - s.getChars(pos + 1, s.length(), sb, pos); - s = new String(sb); - } + // Currently unused. + // String s = pfm.getPostscriptName(); + // int pos = s.indexOf("-"); + // if (pos >= 0) { + // char[] sb = new char[s.length() - 1]; + // s.getChars(0, pos, sb, 0); + // s.getChars(pos + 1, s.length(), sb, pos); + // s = new String(sb); + // } el = doc.createElement("embed"); root.appendChild(el); @@ -304,8 +305,7 @@ public class PFMReader extends AbstractFontReader { el = doc.createElement("char"); widths.appendChild(el); el.setAttribute("idx", Integer.toString(i)); - el.setAttribute("wdt", - new Integer(pfm.getCharWidth(i)).toString()); + el.setAttribute("wdt", Integer.toString(pfm.getCharWidth(i))); } @@ -318,13 +318,14 @@ public class PFMReader extends AbstractFontReader { root.appendChild(el); Element el2 = null; - Map h2 = (Map)pfm.getKerning().get(kpx1); - Iterator enum2 = h2.keySet().iterator(); + Map h2 = (Map) pfm.getKerning().get(kpx1); + Iterator enum2 = h2.entrySet().iterator(); while (enum2.hasNext()) { - Integer kpx2 = (Integer)enum2.next(); + Map.Entry entry = (Map.Entry) enum2.next(); + Integer kpx2 = (Integer) entry.getKey(); el2 = doc.createElement("pair"); el2.setAttribute("kpx2", kpx2.toString()); - Integer val = (Integer)h2.get(kpx2); + Integer val = (Integer) entry.getValue(); el2.setAttribute("kern", val.toString()); el.appendChild(el2); } diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index 03a3e1018..e2858e2f7 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java @@ -212,7 +212,8 @@ public class FontInfoFinder { IOUtils.closeQuietly(in); } - List embedFontInfoList = new java.util.ArrayList(); //List<EmbedFontInfo> + List/*<EmbedFontInfo>*/ embedFontInfoList + = new java.util.ArrayList/*<EmbedFontInfo>*/(); // For each font name ... //for (String fontName : ttcNames) { diff --git a/src/java/org/apache/fop/fonts/type1/PFMFile.java b/src/java/org/apache/fop/fonts/type1/PFMFile.java index d2d587d90..d1a3d79ff 100644 --- a/src/java/org/apache/fop/fonts/type1/PFMFile.java +++ b/src/java/org/apache/fop/fonts/type1/PFMFile.java @@ -481,7 +481,13 @@ public class PFMFile { * @return The width of a character. */ public int getCharWidth(short which) { - return extentTable[which - dfFirstChar]; + if (extentTable != null) { + return extentTable[which - dfFirstChar]; + } else { + //Fixed-width font (PFM may have no extent table) + //we'll just use the average width + return this.dfAvgWidth; + } } } diff --git a/src/java/org/apache/fop/hyphenation/PatternParser.java b/src/java/org/apache/fop/hyphenation/PatternParser.java index 6bd423a26..b34ab7ec8 100644 --- a/src/java/org/apache/fop/hyphenation/PatternParser.java +++ b/src/java/org/apache/fop/hyphenation/PatternParser.java @@ -30,7 +30,10 @@ import org.xml.sax.Attributes; // Java import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; import java.net.MalformedURLException; import java.util.ArrayList; @@ -44,13 +47,14 @@ import javax.xml.parsers.SAXParserFactory; */ public class PatternParser extends DefaultHandler implements PatternConsumer { - XMLReader parser; - int currElement; - PatternConsumer consumer; - StringBuffer token; - ArrayList exception; - char hyphenChar; - String errMsg; + private XMLReader parser; + private int currElement; + private PatternConsumer consumer; + private StringBuffer token; + private ArrayList exception; + private char hyphenChar; + private String errMsg; + private boolean hasClasses = false; static final int ELEM_CLASSES = 1; static final int ELEM_EXCEPTIONS = 2; @@ -58,24 +62,19 @@ public class PatternParser extends DefaultHandler implements PatternConsumer { static final int ELEM_HYPHEN = 4; public PatternParser() throws HyphenationException { + this.consumer = this; token = new StringBuffer(); parser = createParser(); parser.setContentHandler(this); parser.setErrorHandler(this); hyphenChar = '-'; // default - } - public PatternParser(PatternConsumer consumer) - throws HyphenationException { + public PatternParser(PatternConsumer consumer) throws HyphenationException { this(); this.consumer = consumer; } - public void setConsumer(PatternConsumer consumer) { - this.consumer = consumer; - } - /** * Parses a hyphenation pattern file. * @param filename the filename @@ -249,15 +248,32 @@ public class PatternParser extends DefaultHandler implements PatternConsumer { return il.toString(); } + protected void getExternalClasses() throws SAXException { + XMLReader mainParser = parser; + parser = createParser(); + parser.setContentHandler(this); + parser.setErrorHandler(this); + InputStream stream = this.getClass().getResourceAsStream("classes.xml"); + InputSource source = new InputSource(stream); + try { + parser.parse(source); + } catch (IOException ioe) { + throw new SAXException(ioe.getMessage()); + } finally { + parser = mainParser; + } + } + // // ContentHandler methods // /** * {@inheritDoc} + * @throws SAXException */ public void startElement(String uri, String local, String raw, - Attributes attrs) { + Attributes attrs) throws SAXException { if (local.equals("hyphen-char")) { String h = attrs.getValue("value"); if (h != null && h.length() == 1) { @@ -266,8 +282,14 @@ public class PatternParser extends DefaultHandler implements PatternConsumer { } else if (local.equals("classes")) { currElement = ELEM_CLASSES; } else if (local.equals("patterns")) { + if (!hasClasses) { + getExternalClasses(); + } currElement = ELEM_PATTERNS; } else if (local.equals("exceptions")) { + if (!hasClasses) { + getExternalClasses(); + } currElement = ELEM_EXCEPTIONS; exception = new ArrayList(); } else if (local.equals("hyphen")) { @@ -311,6 +333,9 @@ public class PatternParser extends DefaultHandler implements PatternConsumer { token.setLength(0); } } + if (currElement == ELEM_CLASSES) { + hasClasses = true; + } if (currElement == ELEM_HYPHEN) { currElement = ELEM_EXCEPTIONS; } else { @@ -403,23 +428,46 @@ public class PatternParser extends DefaultHandler implements PatternConsumer { // PatternConsumer implementation for testing purposes public void addClass(String c) { - System.out.println("class: " + c); + testOut.println("class: " + c); } public void addException(String w, ArrayList e) { - System.out.println("exception: " + w + " : " + e.toString()); + testOut.println("exception: " + w + " : " + e.toString()); } public void addPattern(String p, String v) { - System.out.println("pattern: " + p + " : " + v); + testOut.println("pattern: " + p + " : " + v); + } + + private PrintStream testOut = System.out; + + /** + * @param testOut the testOut to set + */ + public void setTestOut(PrintStream testOut) { + this.testOut = testOut; + } + + public void closeTestOut() { + testOut.flush(); + testOut.close(); } public static void main(String[] args) throws Exception { if (args.length > 0) { PatternParser pp = new PatternParser(); - pp.setConsumer(pp); + PrintStream p = null; + if (args.length > 1) { + FileOutputStream f = new FileOutputStream(args[1]); + p = new PrintStream(f, false, "utf-8"); + pp.setTestOut(p); + } pp.parse(args[0]); + if (pp != null) { + pp.closeTestOut(); + } } } + } diff --git a/src/java/org/apache/fop/hyphenation/SerializeHyphPattern.java b/src/java/org/apache/fop/hyphenation/SerializeHyphPattern.java new file mode 100644 index 000000000..d2a259db0 --- /dev/null +++ b/src/java/org/apache/fop/hyphenation/SerializeHyphPattern.java @@ -0,0 +1,138 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.hyphenation; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.ObjectOutputStream; + +/** + * Serialize hyphenation patterns + * For all xml files in the source directory a pattern file is built in the target directory + * This class may be called from the ant build file in a java task + */ +public class SerializeHyphPattern { + + private boolean errorDump = false; + + /** + * Controls the amount of error information dumped. + * @param errorDump True if more error info should be provided + */ + public void setErrorDump(boolean errorDump) { + this.errorDump = errorDump; + } + + /** + * Compile all xml files in sourceDir, and write output hyp files in targetDir + * @param sourceDir Directory with pattern xml files + * @param targetDir Directory to which compiled pattern hyp files should be written + */ + public void serializeDir(File sourceDir, File targetDir) { + final String extension = ".xml"; + String[] sourceFiles = sourceDir.list(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(extension); + } + }); + for (int j = 0; j < sourceFiles.length; j++) { + File infile = new File(sourceDir, sourceFiles[j]); + String outfilename = sourceFiles[j].substring(0, sourceFiles[j].length() + - extension.length()) + ".hyp"; + File outfile = new File(targetDir, outfilename); + serializeFile(infile, outfile); + } + } + + /* + * checks whether input or output files exists or the latter is older than input file + * and start build if necessary + */ + private void serializeFile(File infile, File outfile) { + boolean startProcess; + startProcess = rebuild(infile, outfile); + if (startProcess) { + HyphenationTree hTree = buildPatternFile(infile); + // serialize class + try { + ObjectOutputStream out = new ObjectOutputStream( + new java.io.BufferedOutputStream( + new java.io.FileOutputStream(outfile))); + out.writeObject(hTree); + out.close(); + } catch (IOException ioe) { + System.err.println("Can't write compiled pattern file: " + + outfile); + System.err.println(ioe); + } + } + } + + /* + * serializes pattern files + */ + private HyphenationTree buildPatternFile(File infile) { + System.out.println("Processing " + infile); + HyphenationTree hTree = new HyphenationTree(); + try { + hTree.loadPatterns(infile.toString()); + if (errorDump) { + System.out.println("Stats: "); + hTree.printStats(); + } + } catch (HyphenationException ex) { + System.err.println("Can't load patterns from xml file " + infile + + " - Maybe hyphenation.dtd is missing?"); + if (errorDump) { + System.err.println(ex.toString()); + } + } + return hTree; + } + + /** + * Checks for existence of output file and compares + * dates with input and stylesheet file + */ + private boolean rebuild(File infile, File outfile) { + if (outfile.exists()) { + // checks whether output file is older than input file + if (outfile.lastModified() < infile.lastModified()) { + return true; + } + } else { + // if output file does not exist, start process + return true; + } + return false; + } // end rebuild + + + /** + * Entry point for ant java task + * @param args sourceDir, targetDir + */ + public static void main (String[] args) { + SerializeHyphPattern ser = new SerializeHyphPattern(); + ser.serializeDir(new File(args[0]), new File(args[1])); + } + +} diff --git a/src/java/org/apache/fop/hyphenation/classes.xml b/src/java/org/apache/fop/hyphenation/classes.xml new file mode 100644 index 000000000..056a533a8 --- /dev/null +++ b/src/java/org/apache/fop/hyphenation/classes.xml @@ -0,0 +1,7652 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. --> + +<!-- $Id$ --> + +<!-- !!! THIS IS A GENERATED FILE !!! --> +<!-- If updates are needed, then: --> +<!-- * run 'ant codegen-hyphenation-classes', --> +<!-- which will generate a new file classes.xml --> +<!-- in 'src/java/org/apache/fop/hyphenation' --> +<!-- * commit the changed file --> + +<classes> +aA +bB +cC +dD +eE +fF +gG +hH +iI +jJ +kK +lL +mM +nN +oO +pP +qQ +rR +sS +tT +uU +vV +wW +xX +yY +zZ +ª +µΜ +º +ß +àÀ +áÁ +â +ãà +äÄ +åÅ +æÆ +çÇ +èÈ +éÉ +êÊ +ëË +ìÌ +íÍ +îÎ +ïÏ +ðÐ +ñÑ +òÒ +óÓ +ôÔ +õÕ +öÖ +øØ +ùÙ +úÚ +ûÛ +üÜ +ýÝ +þÞ +ÿŸ +āĀ +ăĂ +ąĄ +ćĆ +ĉĈ +ċĊ +čČ +ďĎ +đĐ +ēĒ +ĕĔ +ėĖ +ęĘ +ěĚ +ĝĜ +ğĞ +ġĠ +ģĢ +ĥĤ +ħĦ +ĩĨ +īĪ +ĭĬ +įĮ +ıI +ijIJ +ĵĴ +ķĶ +ĸ +ĺĹ +ļĻ +ľĽ +ŀĿ +łŁ +ńŃ +ņŅ +ňŇ +ʼn +ŋŊ +ōŌ +ŏŎ +őŐ +œŒ +ŕŔ +ŗŖ +řŘ +śŚ +ŝŜ +şŞ +šŠ +ţŢ +ťŤ +ŧŦ +ũŨ +ūŪ +ŭŬ +ůŮ +űŰ +ųŲ +ŵŴ +ŷŶ +źŹ +żŻ +žŽ +ſS +ƀɃ +ƃƂ +ƅƄ +ƈƇ +ƌƋ +ƍ +ƒƑ +ƕǶ +ƙƘ +ƚȽ +ƛ +ƞȠ +ơƠ +ƣƢ +ƥƤ +ƨƧ +ƪ +ƫ +ƭƬ +ưƯ +ƴƳ +ƶƵ +ƹƸ +ƺ +ƻ +ƽƼ +ƾ +ƿǷ +ǀ +ǁ +ǂ +ǃ +džDŽDž +ljLJLj +njNJNj +ǎǍ +ǐǏ +ǒǑ +ǔǓ +ǖǕ +ǘǗ +ǚǙ +ǜǛ +ǝƎ +ǟǞ +ǡǠ +ǣǢ +ǥǤ +ǧǦ +ǩǨ +ǫǪ +ǭǬ +ǯǮ +ǰ +dzDZDz +ǵǴ +ǹǸ +ǻǺ +ǽǼ +ǿǾ +ȁȀ +ȃȂ +ȅȄ +ȇȆ +ȉȈ +ȋȊ +ȍȌ +ȏȎ +ȑȐ +ȓȒ +ȕȔ +ȗȖ +șȘ +țȚ +ȝȜ +ȟȞ +ȡ +ȣȢ +ȥȤ +ȧȦ +ȩȨ +ȫȪ +ȭȬ +ȯȮ +ȱȰ +ȳȲ +ȴ +ȵ +ȶ +ȷ +ȸ +ȹ +ȼȻ +ȿ +ɀ +ɂɁ +ɇɆ +ɉɈ +ɋɊ +ɍɌ +ɏɎ +ɐⱯ +ɑⱭ +ɒ +ɓƁ +ɔƆ +ɕ +ɖƉ +ɗƊ +ɘ +əƏ +ɚ +ɛƐ +ɜ +ɝ +ɞ +ɟ +ɠƓ +ɡ +ɢ +ɣƔ +ɤ +ɥ +ɦ +ɧ +ɨƗ +ɩƖ +ɪ +ɫⱢ +ɬ +ɭ +ɮ +ɯƜ +ɰ +ɱⱮ +ɲƝ +ɳ +ɴ +ɵƟ +ɶ +ɷ +ɸ +ɹ +ɺ +ɻ +ɼ +ɽⱤ +ɾ +ɿ +ʀƦ +ʁ +ʂ +ʃƩ +ʄ +ʅ +ʆ +ʇ +ʈƮ +ʉɄ +ʊƱ +ʋƲ +ʌɅ +ʍ +ʎ +ʏ +ʐ +ʑ +ʒƷ +ʓ +ʔ +ʕ +ʖ +ʗ +ʘ +ʙ +ʚ +ʛ +ʜ +ʝ +ʞ +ʟ +ʠ +ʡ +ʢ +ʣ +ʤ +ʥ +ʦ +ʧ +ʨ +ʩ +ʪ +ʫ +ʬ +ʭ +ʮ +ʯ +ͱͰ +ͳͲ +ͷͶ +ͻϽ +ͼϾ +ͽϿ +ΐ +άΆ +έΈ +ήΉ +ίΊ +ΰ +αΑ +βΒ +γΓ +δΔ +εΕ +ζΖ +ηΗ +θΘ +ιΙ +κΚ +λΛ +μΜ +νΝ +ξΞ +οΟ +πΠ +ρΡ +ςΣ +σΣ +τΤ +υΥ +φΦ +χΧ +ψΨ +ωΩ +ϊΪ +ϋΫ +όΌ +ύΎ +ώΏ +ϐΒ +ϑΘ +ϒ +ϓ +ϔ +ϕΦ +ϖΠ +ϗϏ +ϙϘ +ϛϚ +ϝϜ +ϟϞ +ϡϠ +ϣϢ +ϥϤ +ϧϦ +ϩϨ +ϫϪ +ϭϬ +ϯϮ +ϰΚ +ϱΡ +ϲϹ +ϳ +ϵΕ +ϸϷ +ϻϺ +ϼ +аА +бБ +вВ +гГ +дД +еЕ +жЖ +зЗ +иИ +йЙ +кК +лЛ +мМ +нН +оО +пП +рР +сС +тТ +уУ +фФ +хХ +цЦ +чЧ +шШ +щЩ +ъЪ +ыЫ +ьЬ +эЭ +юЮ +яЯ +ѐЀ +ёЁ +ђЂ +ѓЃ +єЄ +ѕЅ +іІ +їЇ +јЈ +љЉ +њЊ +ћЋ +ќЌ +ѝЍ +ўЎ +џЏ +ѡѠ +ѣѢ +ѥѤ +ѧѦ +ѩѨ +ѫѪ +ѭѬ +ѯѮ +ѱѰ +ѳѲ +ѵѴ +ѷѶ +ѹѸ +ѻѺ +ѽѼ +ѿѾ +ҁҀ +ҋҊ +ҍҌ +ҏҎ +ґҐ +ғҒ +ҕҔ +җҖ +ҙҘ +қҚ +ҝҜ +ҟҞ +ҡҠ +ңҢ +ҥҤ +ҧҦ +ҩҨ +ҫҪ +ҭҬ +үҮ +ұҰ +ҳҲ +ҵҴ +ҷҶ +ҹҸ +һҺ +ҽҼ +ҿҾ +ӂӁ +ӄӃ +ӆӅ +ӈӇ +ӊӉ +ӌӋ +ӎӍ +ӏӀ +ӑӐ +ӓӒ +ӕӔ +ӗӖ +әӘ +ӛӚ +ӝӜ +ӟӞ +ӡӠ +ӣӢ +ӥӤ +ӧӦ +өӨ +ӫӪ +ӭӬ +ӯӮ +ӱӰ +ӳӲ +ӵӴ +ӷӶ +ӹӸ +ӻӺ +ӽӼ +ӿӾ +ԁԀ +ԃԂ +ԅԄ +ԇԆ +ԉԈ +ԋԊ +ԍԌ +ԏԎ +ԑԐ +ԓԒ +ԕԔ +ԗԖ +ԙԘ +ԛԚ +ԝԜ +ԟԞ +ԡԠ +ԣԢ +աԱ +բԲ +գԳ +դԴ +եԵ +զԶ +էԷ +ըԸ +թԹ +ժԺ +իԻ +լԼ +խԽ +ծԾ +կԿ +հՀ +ձՁ +ղՂ +ճՃ +մՄ +յՅ +նՆ +շՇ +ոՈ +չՉ +պՊ +ջՋ +ռՌ +սՍ +վՎ +տՏ +րՐ +ցՑ +ւՒ +փՓ +քՔ +օՕ +ֆՖ +և +א +ב +ג +ד +ה +ו +ז +ח +ט +י +ך +כ +ל +ם +מ +ן +נ +ס +ע +ף +פ +ץ +צ +ק +ר +ש +ת +װ +ױ +ײ +ء +آ +أ +ؤ +إ +ئ +ا +ب +ة +ت +ث +ج +ح +خ +د +ذ +ر +ز +س +ش +ص +ض +ط +ظ +ع +غ +ػ +ؼ +ؽ +ؾ +ؿ +ف +ق +ك +ل +م +ن +ه +و +ى +ي +ٮ +ٯ +ٱ +ٲ +ٳ +ٴ +ٵ +ٶ +ٷ +ٸ +ٹ +ٺ +ٻ +ټ +ٽ +پ +ٿ +ڀ +ځ +ڂ +ڃ +ڄ +څ +چ +ڇ +ڈ +ډ +ڊ +ڋ +ڌ +ڍ +ڎ +ڏ +ڐ +ڑ +ڒ +ړ +ڔ +ڕ +ږ +ڗ +ژ +ڙ +ښ +ڛ +ڜ +ڝ +ڞ +ڟ +ڠ +ڡ +ڢ +ڣ +ڤ +ڥ +ڦ +ڧ +ڨ +ک +ڪ +ګ +ڬ +ڭ +ڮ +گ +ڰ +ڱ +ڲ +ڳ +ڴ +ڵ +ڶ +ڷ +ڸ +ڹ +ں +ڻ +ڼ +ڽ +ھ +ڿ +ۀ +ہ +ۂ +ۃ +ۄ +ۅ +ۆ +ۇ +ۈ +ۉ +ۊ +ۋ +ی +ۍ +ێ +ۏ +ې +ۑ +ے +ۓ +ە +ۮ +ۯ +ۺ +ۻ +ۼ +ۿ +ܐ +ܒ +ܓ +ܔ +ܕ +ܖ +ܗ +ܘ +ܙ +ܚ +ܛ +ܜ +ܝ +ܞ +ܟ +ܠ +ܡ +ܢ +ܣ +ܤ +ܥ +ܦ +ܧ +ܨ +ܩ +ܪ +ܫ +ܬ +ܭ +ܮ +ܯ +ݍ +ݎ +ݏ +ݐ +ݑ +ݒ +ݓ +ݔ +ݕ +ݖ +ݗ +ݘ +ݙ +ݚ +ݛ +ݜ +ݝ +ݞ +ݟ +ݠ +ݡ +ݢ +ݣ +ݤ +ݥ +ݦ +ݧ +ݨ +ݩ +ݪ +ݫ +ݬ +ݭ +ݮ +ݯ +ݰ +ݱ +ݲ +ݳ +ݴ +ݵ +ݶ +ݷ +ݸ +ݹ +ݺ +ݻ +ݼ +ݽ +ݾ +ݿ +ހ +ށ +ނ +ރ +ބ +ޅ +ކ +އ +ވ +މ +ފ +ދ +ތ +ލ +ގ +ޏ +ސ +ޑ +ޒ +ޓ +ޔ +ޕ +ޖ +ޗ +ޘ +ޙ +ޚ +ޛ +ޜ +ޝ +ޞ +ޟ +ޠ +ޡ +ޢ +ޣ +ޤ +ޥ +ޱ +ߊ +ߋ +ߌ +ߍ +ߎ +ߏ +ߐ +ߑ +ߒ +ߓ +ߔ +ߕ +ߖ +ߗ +ߘ +ߙ +ߚ +ߛ +ߜ +ߝ +ߞ +ߟ +ߠ +ߡ +ߢ +ߣ +ߤ +ߥ +ߦ +ߧ +ߨ +ߩ +ߪ +ऄ +अ +आ +इ +ई +उ +ऊ +ऋ +ऌ +ऍ +ऎ +ए +ऐ +ऑ +ऒ +ओ +औ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +ऩ +प +फ +ब +भ +म +य +र +ऱ +ल +ळ +ऴ +व +श +ष +स +ह +ऽ +ॐ +क़ +ख़ +ग़ +ज़ +ड़ +ढ़ +फ़ +य़ +ॠ +ॡ +ॲ +ॻ +ॼ +ॽ +ॾ +ॿ +অ +আ +ই +ঈ +উ +ঊ +ঋ +ঌ +এ +ঐ +ও +ঔ +ক +খ +গ +ঘ +ঙ +চ +ছ +জ +ঝ +ঞ +ট +ঠ +ড +ঢ +ণ +ত +থ +দ +ধ +ন +প +ফ +ব +ভ +ম +য +র +ল +শ +ষ +স +হ +ঽ +ৎ +ড় +ঢ় +য় +ৠ +ৡ +ৰ +ৱ +ਅ +ਆ +ਇ +ਈ +ਉ +ਊ +ਏ +ਐ +ਓ +ਔ +ਕ +ਖ +ਗ +ਘ +ਙ +ਚ +ਛ +ਜ +ਝ +ਞ +ਟ +ਠ +ਡ +ਢ +ਣ +ਤ +ਥ +ਦ +ਧ +ਨ +ਪ +ਫ +ਬ +ਭ +ਮ +ਯ +ਰ +ਲ +ਲ਼ +ਵ +ਸ਼ +ਸ +ਹ +ਖ਼ +ਗ਼ +ਜ਼ +ੜ +ਫ਼ +ੲ +ੳ +ੴ +અ +આ +ઇ +ઈ +ઉ +ઊ +ઋ +ઌ +ઍ +એ +ઐ +ઑ +ઓ +ઔ +ક +ખ +ગ +ઘ +ઙ +ચ +છ +જ +ઝ +ઞ +ટ +ઠ +ડ +ઢ +ણ +ત +થ +દ +ધ +ન +પ +ફ +બ +ભ +મ +ય +ર +લ +ળ +વ +શ +ષ +સ +હ +ઽ +ૐ +ૠ +ૡ +ଅ +ଆ +ଇ +ଈ +ଉ +ଊ +ଋ +ଌ +ଏ +ଐ +ଓ +ଔ +କ +ଖ +ଗ +ଘ +ଙ +ଚ +ଛ +ଜ +ଝ +ଞ +ଟ +ଠ +ଡ +ଢ +ଣ +ତ +ଥ +ଦ +ଧ +ନ +ପ +ଫ +ବ +ଭ +ମ +ଯ +ର +ଲ +ଳ +ଵ +ଶ +ଷ +ସ +ହ +ଽ +ଡ଼ +ଢ଼ +ୟ +ୠ +ୡ +ୱ +ஃ +அ +ஆ +இ +ஈ +உ +ஊ +எ +ஏ +ஐ +ஒ +ஓ +ஔ +க +ங +ச +ஜ +ஞ +ட +ண +த +ந +ன +ப +ம +ய +ர +ற +ல +ள +ழ +வ +ஶ +ஷ +ஸ +ஹ +ௐ +అ +ఆ +ఇ +ఈ +ఉ +ఊ +ఋ +ఌ +ఎ +ఏ +ఐ +ఒ +ఓ +ఔ +క +ఖ +గ +ఘ +ఙ +చ +ఛ +జ +ఝ +ఞ +ట +ఠ +డ +ఢ +ణ +త +థ +ద +ధ +న +ప +ఫ +బ +భ +మ +య +ర +ఱ +ల +ళ +వ +శ +ష +స +హ +ఽ +ౘ +ౙ +ౠ +ౡ +ಅ +ಆ +ಇ +ಈ +ಉ +ಊ +ಋ +ಌ +ಎ +ಏ +ಐ +ಒ +ಓ +ಔ +ಕ +ಖ +ಗ +ಘ +ಙ +ಚ +ಛ +ಜ +ಝ +ಞ +ಟ +ಠ +ಡ +ಢ +ಣ +ತ +ಥ +ದ +ಧ +ನ +ಪ +ಫ +ಬ +ಭ +ಮ +ಯ +ರ +ಱ +ಲ +ಳ +ವ +ಶ +ಷ +ಸ +ಹ +ಽ +ೞ +ೠ +ೡ +അ +ആ +ഇ +ഈ +ഉ +ഊ +ഋ +ഌ +എ +ഏ +ഐ +ഒ +ഓ +ഔ +ക +ഖ +ഗ +ഘ +ങ +ച +ഛ +ജ +ഝ +ഞ +ട +ഠ +ഡ +ഢ +ണ +ത +ഥ +ദ +ധ +ന +പ +ഫ +ബ +ഭ +മ +യ +ര +റ +ല +ള +ഴ +വ +ശ +ഷ +സ +ഹ +ഽ +ൠ +ൡ +ൺ +ൻ +ർ +ൽ +ൾ +ൿ +අ +ආ +ඇ +ඈ +ඉ +ඊ +උ +ඌ +ඍ +ඎ +ඏ +ඐ +එ +ඒ +ඓ +ඔ +ඕ +ඖ +ක +ඛ +ග +ඝ +ඞ +ඟ +ච +ඡ +ජ +ඣ +ඤ +ඥ +ඦ +ට +ඨ +ඩ +ඪ +ණ +ඬ +ත +ථ +ද +ධ +න +ඳ +ප +ඵ +බ +භ +ම +ඹ +ය +ර +ල +ව +ශ +ෂ +ස +හ +ළ +ෆ +ก +ข +ฃ +ค +ฅ +ฆ +ง +จ +ฉ +ช +ซ +ฌ +ญ +ฎ +ฏ +ฐ +ฑ +ฒ +ณ +ด +ต +ถ +ท +ธ +น +บ +ป +ผ +ฝ +พ +ฟ +ภ +ม +ย +ร +ฤ +ล +ฦ +ว +ศ +ษ +ส +ห +ฬ +อ +ฮ +ฯ +ะ +า +ำ +เ +แ +โ +ใ +ไ +ๅ +ກ +ຂ +ຄ +ງ +ຈ +ຊ +ຍ +ດ +ຕ +ຖ +ທ +ນ +ບ +ປ +ຜ +ຝ +ພ +ຟ +ມ +ຢ +ຣ +ລ +ວ +ສ +ຫ +ອ +ຮ +ຯ +ະ +າ +ຳ +ຽ +ເ +ແ +ໂ +ໃ +ໄ +ໜ +ໝ +ༀ +ཀ +ཁ +ག +གྷ +ང +ཅ +ཆ +ཇ +ཉ +ཊ +ཋ +ཌ +ཌྷ +ཎ +ཏ +ཐ +ད +དྷ +ན +པ +ཕ +བ +བྷ +མ +ཙ +ཚ +ཛ +ཛྷ +ཝ +ཞ +ཟ +འ +ཡ +ར +ལ +ཤ +ཥ +ས +ཧ +ཨ +ཀྵ +ཪ +ཫ +ཬ +ྈ +ྉ +ྊ +ྋ +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ဿ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၚ +ၛ +ၜ +ၝ +ၡ +ၥ +ၦ +ၮ +ၯ +ၰ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႎ +ა +ბ +გ +დ +ე +ვ +ზ +თ +ი +კ +ლ +მ +ნ +ო +პ +ჟ +რ +ს +ტ +უ +ფ +ქ +ღ +ყ +შ +ჩ +ც +ძ +წ +ჭ +ხ +ჯ +ჰ +ჱ +ჲ +ჳ +ჴ +ჵ +ჶ +ჷ +ჸ +ჹ +ჺ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ +ᄓ +ᄔ +ᄕ +ᄖ +ᄗ +ᄘ +ᄙ +ᄚ +ᄛ +ᄜ +ᄝ +ᄞ +ᄟ +ᄠ +ᄡ +ᄢ +ᄣ +ᄤ +ᄥ +ᄦ +ᄧ +ᄨ +ᄩ +ᄪ +ᄫ +ᄬ +ᄭ +ᄮ +ᄯ +ᄰ +ᄱ +ᄲ +ᄳ +ᄴ +ᄵ +ᄶ +ᄷ +ᄸ +ᄹ +ᄺ +ᄻ +ᄼ +ᄽ +ᄾ +ᄿ +ᅀ +ᅁ +ᅂ +ᅃ +ᅄ +ᅅ +ᅆ +ᅇ +ᅈ +ᅉ +ᅊ +ᅋ +ᅌ +ᅍ +ᅎ +ᅏ +ᅐ +ᅑ +ᅒ +ᅓ +ᅔ +ᅕ +ᅖ +ᅗ +ᅘ +ᅙ +ᅟ +ᅠ +ᅡ +ᅢ +ᅣ +ᅤ +ᅥ +ᅦ +ᅧ +ᅨ +ᅩ +ᅪ +ᅫ +ᅬ +ᅭ +ᅮ +ᅯ +ᅰ +ᅱ +ᅲ +ᅳ +ᅴ +ᅵ +ᅶ +ᅷ +ᅸ +ᅹ +ᅺ +ᅻ +ᅼ +ᅽ +ᅾ +ᅿ +ᆀ +ᆁ +ᆂ +ᆃ +ᆄ +ᆅ +ᆆ +ᆇ +ᆈ +ᆉ +ᆊ +ᆋ +ᆌ +ᆍ +ᆎ +ᆏ +ᆐ +ᆑ +ᆒ +ᆓ +ᆔ +ᆕ +ᆖ +ᆗ +ᆘ +ᆙ +ᆚ +ᆛ +ᆜ +ᆝ +ᆞ +ᆟ +ᆠ +ᆡ +ᆢ +ᆨ +ᆩ +ᆪ +ᆫ +ᆬ +ᆭ +ᆮ +ᆯ +ᆰ +ᆱ +ᆲ +ᆳ +ᆴ +ᆵ +ᆶ +ᆷ +ᆸ +ᆹ +ᆺ +ᆻ +ᆼ +ᆽ +ᆾ +ᆿ +ᇀ +ᇁ +ᇂ +ᇃ +ᇄ +ᇅ +ᇆ +ᇇ +ᇈ +ᇉ +ᇊ +ᇋ +ᇌ +ᇍ +ᇎ +ᇏ +ᇐ +ᇑ +ᇒ +ᇓ +ᇔ +ᇕ +ᇖ +ᇗ +ᇘ +ᇙ +ᇚ +ᇛ +ᇜ +ᇝ +ᇞ +ᇟ +ᇠ +ᇡ +ᇢ +ᇣ +ᇤ +ᇥ +ᇦ +ᇧ +ᇨ +ᇩ +ᇪ +ᇫ +ᇬ +ᇭ +ᇮ +ᇯ +ᇰ +ᇱ +ᇲ +ᇳ +ᇴ +ᇵ +ᇶ +ᇷ +ᇸ +ᇹ +ሀ +ሁ +ሂ +ሃ +ሄ +ህ +ሆ +ሇ +ለ +ሉ +ሊ +ላ +ሌ +ል +ሎ +ሏ +ሐ +ሑ +ሒ +ሓ +ሔ +ሕ +ሖ +ሗ +መ +ሙ +ሚ +ማ +ሜ +ም +ሞ +ሟ +ሠ +ሡ +ሢ +ሣ +ሤ +ሥ +ሦ +ሧ +ረ +ሩ +ሪ +ራ +ሬ +ር +ሮ +ሯ +ሰ +ሱ +ሲ +ሳ +ሴ +ስ +ሶ +ሷ +ሸ +ሹ +ሺ +ሻ +ሼ +ሽ +ሾ +ሿ +ቀ +ቁ +ቂ +ቃ +ቄ +ቅ +ቆ +ቇ +ቈ +ቊ +ቋ +ቌ +ቍ +ቐ +ቑ +ቒ +ቓ +ቔ +ቕ +ቖ +ቘ +ቚ +ቛ +ቜ +ቝ +በ +ቡ +ቢ +ባ +ቤ +ብ +ቦ +ቧ +ቨ +ቩ +ቪ +ቫ +ቬ +ቭ +ቮ +ቯ +ተ +ቱ +ቲ +ታ +ቴ +ት +ቶ +ቷ +ቸ +ቹ +ቺ +ቻ +ቼ +ች +ቾ +ቿ +ኀ +ኁ +ኂ +ኃ +ኄ +ኅ +ኆ +ኇ +ኈ +ኊ +ኋ +ኌ +ኍ +ነ +ኑ +ኒ +ና +ኔ +ን +ኖ +ኗ +ኘ +ኙ +ኚ +ኛ +ኜ +ኝ +ኞ +ኟ +አ +ኡ +ኢ +ኣ +ኤ +እ +ኦ +ኧ +ከ +ኩ +ኪ +ካ +ኬ +ክ +ኮ +ኯ +ኰ +ኲ +ኳ +ኴ +ኵ +ኸ +ኹ +ኺ +ኻ +ኼ +ኽ +ኾ +ዀ +ዂ +ዃ +ዄ +ዅ +ወ +ዉ +ዊ +ዋ +ዌ +ው +ዎ +ዏ +ዐ +ዑ +ዒ +ዓ +ዔ +ዕ +ዖ +ዘ +ዙ +ዚ +ዛ +ዜ +ዝ +ዞ +ዟ +ዠ +ዡ +ዢ +ዣ +ዤ +ዥ +ዦ +ዧ +የ +ዩ +ዪ +ያ +ዬ +ይ +ዮ +ዯ +ደ +ዱ +ዲ +ዳ +ዴ +ድ +ዶ +ዷ +ዸ +ዹ +ዺ +ዻ +ዼ +ዽ +ዾ +ዿ +ጀ +ጁ +ጂ +ጃ +ጄ +ጅ +ጆ +ጇ +ገ +ጉ +ጊ +ጋ +ጌ +ግ +ጎ +ጏ +ጐ +ጒ +ጓ +ጔ +ጕ +ጘ +ጙ +ጚ +ጛ +ጜ +ጝ +ጞ +ጟ +ጠ +ጡ +ጢ +ጣ +ጤ +ጥ +ጦ +ጧ +ጨ +ጩ +ጪ +ጫ +ጬ +ጭ +ጮ +ጯ +ጰ +ጱ +ጲ +ጳ +ጴ +ጵ +ጶ +ጷ +ጸ +ጹ +ጺ +ጻ +ጼ +ጽ +ጾ +ጿ +ፀ +ፁ +ፂ +ፃ +ፄ +ፅ +ፆ +ፇ +ፈ +ፉ +ፊ +ፋ +ፌ +ፍ +ፎ +ፏ +ፐ +ፑ +ፒ +ፓ +ፔ +ፕ +ፖ +ፗ +ፘ +ፙ +ፚ +ᎀ +ᎁ +ᎂ +ᎃ +ᎄ +ᎅ +ᎆ +ᎇ +ᎈ +ᎉ +ᎊ +ᎋ +ᎌ +ᎍ +ᎎ +ᎏ +Ꭰ +Ꭱ +Ꭲ +Ꭳ +Ꭴ +Ꭵ +Ꭶ +Ꭷ +Ꭸ +Ꭹ +Ꭺ +Ꭻ +Ꭼ +Ꭽ +Ꭾ +Ꭿ +Ꮀ +Ꮁ +Ꮂ +Ꮃ +Ꮄ +Ꮅ +Ꮆ +Ꮇ +Ꮈ +Ꮉ +Ꮊ +Ꮋ +Ꮌ +Ꮍ +Ꮎ +Ꮏ +Ꮐ +Ꮑ +Ꮒ +Ꮓ +Ꮔ +Ꮕ +Ꮖ +Ꮗ +Ꮘ +Ꮙ +Ꮚ +Ꮛ +Ꮜ +Ꮝ +Ꮞ +Ꮟ +Ꮠ +Ꮡ +Ꮢ +Ꮣ +Ꮤ +Ꮥ +Ꮦ +Ꮧ +Ꮨ +Ꮩ +Ꮪ +Ꮫ +Ꮬ +Ꮭ +Ꮮ +Ꮯ +Ꮰ +Ꮱ +Ꮲ +Ꮳ +Ꮴ +Ꮵ +Ꮶ +Ꮷ +Ꮸ +Ꮹ +Ꮺ +Ꮻ +Ꮼ +Ꮽ +Ꮾ +Ꮿ +Ᏸ +Ᏹ +Ᏺ +Ᏻ +Ᏼ +ᐁ +ᐂ +ᐃ +ᐄ +ᐅ +ᐆ +ᐇ +ᐈ +ᐉ +ᐊ +ᐋ +ᐌ +ᐍ +ᐎ +ᐏ +ᐐ +ᐑ +ᐒ +ᐓ +ᐔ +ᐕ +ᐖ +ᐗ +ᐘ +ᐙ +ᐚ +ᐛ +ᐜ +ᐝ +ᐞ +ᐟ +ᐠ +ᐡ +ᐢ +ᐣ +ᐤ +ᐥ +ᐦ +ᐧ +ᐨ +ᐩ +ᐪ +ᐫ +ᐬ +ᐭ +ᐮ +ᐯ +ᐰ +ᐱ +ᐲ +ᐳ +ᐴ +ᐵ +ᐶ +ᐷ +ᐸ +ᐹ +ᐺ +ᐻ +ᐼ +ᐽ +ᐾ +ᐿ +ᑀ +ᑁ +ᑂ +ᑃ +ᑄ +ᑅ +ᑆ +ᑇ +ᑈ +ᑉ +ᑊ +ᑋ +ᑌ +ᑍ +ᑎ +ᑏ +ᑐ +ᑑ +ᑒ +ᑓ +ᑔ +ᑕ +ᑖ +ᑗ +ᑘ +ᑙ +ᑚ +ᑛ +ᑜ +ᑝ +ᑞ +ᑟ +ᑠ +ᑡ +ᑢ +ᑣ +ᑤ +ᑥ +ᑦ +ᑧ +ᑨ +ᑩ +ᑪ +ᑫ +ᑬ +ᑭ +ᑮ +ᑯ +ᑰ +ᑱ +ᑲ +ᑳ +ᑴ +ᑵ +ᑶ +ᑷ +ᑸ +ᑹ +ᑺ +ᑻ +ᑼ +ᑽ +ᑾ +ᑿ +ᒀ +ᒁ +ᒂ +ᒃ +ᒄ +ᒅ +ᒆ +ᒇ +ᒈ +ᒉ +ᒊ +ᒋ +ᒌ +ᒍ +ᒎ +ᒏ +ᒐ +ᒑ +ᒒ +ᒓ +ᒔ +ᒕ +ᒖ +ᒗ +ᒘ +ᒙ +ᒚ +ᒛ +ᒜ +ᒝ +ᒞ +ᒟ +ᒠ +ᒡ +ᒢ +ᒣ +ᒤ +ᒥ +ᒦ +ᒧ +ᒨ +ᒩ +ᒪ +ᒫ +ᒬ +ᒭ +ᒮ +ᒯ +ᒰ +ᒱ +ᒲ +ᒳ +ᒴ +ᒵ +ᒶ +ᒷ +ᒸ +ᒹ +ᒺ +ᒻ +ᒼ +ᒽ +ᒾ +ᒿ +ᓀ +ᓁ +ᓂ +ᓃ +ᓄ +ᓅ +ᓆ +ᓇ +ᓈ +ᓉ +ᓊ +ᓋ +ᓌ +ᓍ +ᓎ +ᓏ +ᓐ +ᓑ +ᓒ +ᓓ +ᓔ +ᓕ +ᓖ +ᓗ +ᓘ +ᓙ +ᓚ +ᓛ +ᓜ +ᓝ +ᓞ +ᓟ +ᓠ +ᓡ +ᓢ +ᓣ +ᓤ +ᓥ +ᓦ +ᓧ +ᓨ +ᓩ +ᓪ +ᓫ +ᓬ +ᓭ +ᓮ +ᓯ +ᓰ +ᓱ +ᓲ +ᓳ +ᓴ +ᓵ +ᓶ +ᓷ +ᓸ +ᓹ +ᓺ +ᓻ +ᓼ +ᓽ +ᓾ +ᓿ +ᔀ +ᔁ +ᔂ +ᔃ +ᔄ +ᔅ +ᔆ +ᔇ +ᔈ +ᔉ +ᔊ +ᔋ +ᔌ +ᔍ +ᔎ +ᔏ +ᔐ +ᔑ +ᔒ +ᔓ +ᔔ +ᔕ +ᔖ +ᔗ +ᔘ +ᔙ +ᔚ +ᔛ +ᔜ +ᔝ +ᔞ +ᔟ +ᔠ +ᔡ +ᔢ +ᔣ +ᔤ +ᔥ +ᔦ +ᔧ +ᔨ +ᔩ +ᔪ +ᔫ +ᔬ +ᔭ +ᔮ +ᔯ +ᔰ +ᔱ +ᔲ +ᔳ +ᔴ +ᔵ +ᔶ +ᔷ +ᔸ +ᔹ +ᔺ +ᔻ +ᔼ +ᔽ +ᔾ +ᔿ +ᕀ +ᕁ +ᕂ +ᕃ +ᕄ +ᕅ +ᕆ +ᕇ +ᕈ +ᕉ +ᕊ +ᕋ +ᕌ +ᕍ +ᕎ +ᕏ +ᕐ +ᕑ +ᕒ +ᕓ +ᕔ +ᕕ +ᕖ +ᕗ +ᕘ +ᕙ +ᕚ +ᕛ +ᕜ +ᕝ +ᕞ +ᕟ +ᕠ +ᕡ +ᕢ +ᕣ +ᕤ +ᕥ +ᕦ +ᕧ +ᕨ +ᕩ +ᕪ +ᕫ +ᕬ +ᕭ +ᕮ +ᕯ +ᕰ +ᕱ +ᕲ +ᕳ +ᕴ +ᕵ +ᕶ +ᕷ +ᕸ +ᕹ +ᕺ +ᕻ +ᕼ +ᕽ +ᕾ +ᕿ +ᖀ +ᖁ +ᖂ +ᖃ +ᖄ +ᖅ +ᖆ +ᖇ +ᖈ +ᖉ +ᖊ +ᖋ +ᖌ +ᖍ +ᖎ +ᖏ +ᖐ +ᖑ +ᖒ +ᖓ +ᖔ +ᖕ +ᖖ +ᖗ +ᖘ +ᖙ +ᖚ +ᖛ +ᖜ +ᖝ +ᖞ +ᖟ +ᖠ +ᖡ +ᖢ +ᖣ +ᖤ +ᖥ +ᖦ +ᖧ +ᖨ +ᖩ +ᖪ +ᖫ +ᖬ +ᖭ +ᖮ +ᖯ +ᖰ +ᖱ +ᖲ +ᖳ +ᖴ +ᖵ +ᖶ +ᖷ +ᖸ +ᖹ +ᖺ +ᖻ +ᖼ +ᖽ +ᖾ +ᖿ +ᗀ +ᗁ +ᗂ +ᗃ +ᗄ +ᗅ +ᗆ +ᗇ +ᗈ +ᗉ +ᗊ +ᗋ +ᗌ +ᗍ +ᗎ +ᗏ +ᗐ +ᗑ +ᗒ +ᗓ +ᗔ +ᗕ +ᗖ +ᗗ +ᗘ +ᗙ +ᗚ +ᗛ +ᗜ +ᗝ +ᗞ +ᗟ +ᗠ +ᗡ +ᗢ +ᗣ +ᗤ +ᗥ +ᗦ +ᗧ +ᗨ +ᗩ +ᗪ +ᗫ +ᗬ +ᗭ +ᗮ +ᗯ +ᗰ +ᗱ +ᗲ +ᗳ +ᗴ +ᗵ +ᗶ +ᗷ +ᗸ +ᗹ +ᗺ +ᗻ +ᗼ +ᗽ +ᗾ +ᗿ +ᘀ +ᘁ +ᘂ +ᘃ +ᘄ +ᘅ +ᘆ +ᘇ +ᘈ +ᘉ +ᘊ +ᘋ +ᘌ +ᘍ +ᘎ +ᘏ +ᘐ +ᘑ +ᘒ +ᘓ +ᘔ +ᘕ +ᘖ +ᘗ +ᘘ +ᘙ +ᘚ +ᘛ +ᘜ +ᘝ +ᘞ +ᘟ +ᘠ +ᘡ +ᘢ +ᘣ +ᘤ +ᘥ +ᘦ +ᘧ +ᘨ +ᘩ +ᘪ +ᘫ +ᘬ +ᘭ +ᘮ +ᘯ +ᘰ +ᘱ +ᘲ +ᘳ +ᘴ +ᘵ +ᘶ +ᘷ +ᘸ +ᘹ +ᘺ +ᘻ +ᘼ +ᘽ +ᘾ +ᘿ +ᙀ +ᙁ +ᙂ +ᙃ +ᙄ +ᙅ +ᙆ +ᙇ +ᙈ +ᙉ +ᙊ +ᙋ +ᙌ +ᙍ +ᙎ +ᙏ +ᙐ +ᙑ +ᙒ +ᙓ +ᙔ +ᙕ +ᙖ +ᙗ +ᙘ +ᙙ +ᙚ +ᙛ +ᙜ +ᙝ +ᙞ +ᙟ +ᙠ +ᙡ +ᙢ +ᙣ +ᙤ +ᙥ +ᙦ +ᙧ +ᙨ +ᙩ +ᙪ +ᙫ +ᙬ +ᙯ +ᙰ +ᙱ +ᙲ +ᙳ +ᙴ +ᙵ +ᙶ +ᚁ +ᚂ +ᚃ +ᚄ +ᚅ +ᚆ +ᚇ +ᚈ +ᚉ +ᚊ +ᚋ +ᚌ +ᚍ +ᚎ +ᚏ +ᚐ +ᚑ +ᚒ +ᚓ +ᚔ +ᚕ +ᚖ +ᚗ +ᚘ +ᚙ +ᚚ +ᚠ +ᚡ +ᚢ +ᚣ +ᚤ +ᚥ +ᚦ +ᚧ +ᚨ +ᚩ +ᚪ +ᚫ +ᚬ +ᚭ +ᚮ +ᚯ +ᚰ +ᚱ +ᚲ +ᚳ +ᚴ +ᚵ +ᚶ +ᚷ +ᚸ +ᚹ +ᚺ +ᚻ +ᚼ +ᚽ +ᚾ +ᚿ +ᛀ +ᛁ +ᛂ +ᛃ +ᛄ +ᛅ +ᛆ +ᛇ +ᛈ +ᛉ +ᛊ +ᛋ +ᛌ +ᛍ +ᛎ +ᛏ +ᛐ +ᛑ +ᛒ +ᛓ +ᛔ +ᛕ +ᛖ +ᛗ +ᛘ +ᛙ +ᛚ +ᛛ +ᛜ +ᛝ +ᛞ +ᛟ +ᛠ +ᛡ +ᛢ +ᛣ +ᛤ +ᛥ +ᛦ +ᛧ +ᛨ +ᛩ +ᛪ +ᜀ +ᜁ +ᜂ +ᜃ +ᜄ +ᜅ +ᜆ +ᜇ +ᜈ +ᜉ +ᜊ +ᜋ +ᜌ +ᜎ +ᜏ +ᜐ +ᜑ +ᜠ +ᜡ +ᜢ +ᜣ +ᜤ +ᜥ +ᜦ +ᜧ +ᜨ +ᜩ +ᜪ +ᜫ +ᜬ +ᜭ +ᜮ +ᜯ +ᜰ +ᜱ +ᝀ +ᝁ +ᝂ +ᝃ +ᝄ +ᝅ +ᝆ +ᝇ +ᝈ +ᝉ +ᝊ +ᝋ +ᝌ +ᝍ +ᝎ +ᝏ +ᝐ +ᝑ +ᝠ +ᝡ +ᝢ +ᝣ +ᝤ +ᝥ +ᝦ +ᝧ +ᝨ +ᝩ +ᝪ +ᝫ +ᝬ +ᝮ +ᝯ +ᝰ +ក +ខ +គ +ឃ +ង +ច +ឆ +ជ +ឈ +ញ +ដ +ឋ +ឌ +ឍ +ណ +ត +ថ +ទ +ធ +ន +ប +ផ +ព +ភ +ម +យ +រ +ល +វ +ឝ +ឞ +ស +ហ +ឡ +អ +ឣ +ឤ +ឥ +ឦ +ឧ +ឨ +ឩ +ឪ +ឫ +ឬ +ឭ +ឮ +ឯ +ឰ +ឱ +ឲ +ឳ +ៜ +ᠠ +ᠡ +ᠢ +ᠣ +ᠤ +ᠥ +ᠦ +ᠧ +ᠨ +ᠩ +ᠪ +ᠫ +ᠬ +ᠭ +ᠮ +ᠯ +ᠰ +ᠱ +ᠲ +ᠳ +ᠴ +ᠵ +ᠶ +ᠷ +ᠸ +ᠹ +ᠺ +ᠻ +ᠼ +ᠽ +ᠾ +ᠿ +ᡀ +ᡁ +ᡂ +ᡄ +ᡅ +ᡆ +ᡇ +ᡈ +ᡉ +ᡊ +ᡋ +ᡌ +ᡍ +ᡎ +ᡏ +ᡐ +ᡑ +ᡒ +ᡓ +ᡔ +ᡕ +ᡖ +ᡗ +ᡘ +ᡙ +ᡚ +ᡛ +ᡜ +ᡝ +ᡞ +ᡟ +ᡠ +ᡡ +ᡢ +ᡣ +ᡤ +ᡥ +ᡦ +ᡧ +ᡨ +ᡩ +ᡪ +ᡫ +ᡬ +ᡭ +ᡮ +ᡯ +ᡰ +ᡱ +ᡲ +ᡳ +ᡴ +ᡵ +ᡶ +ᡷ +ᢀ +ᢁ +ᢂ +ᢃ +ᢄ +ᢅ +ᢆ +ᢇ +ᢈ +ᢉ +ᢊ +ᢋ +ᢌ +ᢍ +ᢎ +ᢏ +ᢐ +ᢑ +ᢒ +ᢓ +ᢔ +ᢕ +ᢖ +ᢗ +ᢘ +ᢙ +ᢚ +ᢛ +ᢜ +ᢝ +ᢞ +ᢟ +ᢠ +ᢡ +ᢢ +ᢣ +ᢤ +ᢥ +ᢦ +ᢧ +ᢨ +ᢪ +ᤀ +ᤁ +ᤂ +ᤃ +ᤄ +ᤅ +ᤆ +ᤇ +ᤈ +ᤉ +ᤊ +ᤋ +ᤌ +ᤍ +ᤎ +ᤏ +ᤐ +ᤑ +ᤒ +ᤓ +ᤔ +ᤕ +ᤖ +ᤗ +ᤘ +ᤙ +ᤚ +ᤛ +ᤜ +ᥐ +ᥑ +ᥒ +ᥓ +ᥔ +ᥕ +ᥖ +ᥗ +ᥘ +ᥙ +ᥚ +ᥛ +ᥜ +ᥝ +ᥞ +ᥟ +ᥠ +ᥡ +ᥢ +ᥣ +ᥤ +ᥥ +ᥦ +ᥧ +ᥨ +ᥩ +ᥪ +ᥫ +ᥬ +ᥭ +ᥰ +ᥱ +ᥲ +ᥳ +ᥴ +ᦀ +ᦁ +ᦂ +ᦃ +ᦄ +ᦅ +ᦆ +ᦇ +ᦈ +ᦉ +ᦊ +ᦋ +ᦌ +ᦍ +ᦎ +ᦏ +ᦐ +ᦑ +ᦒ +ᦓ +ᦔ +ᦕ +ᦖ +ᦗ +ᦘ +ᦙ +ᦚ +ᦛ +ᦜ +ᦝ +ᦞ +ᦟ +ᦠ +ᦡ +ᦢ +ᦣ +ᦤ +ᦥ +ᦦ +ᦧ +ᦨ +ᦩ +ᧁ +ᧂ +ᧃ +ᧄ +ᧅ +ᧆ +ᧇ +ᨀ +ᨁ +ᨂ +ᨃ +ᨄ +ᨅ +ᨆ +ᨇ +ᨈ +ᨉ +ᨊ +ᨋ +ᨌ +ᨍ +ᨎ +ᨏ +ᨐ +ᨑ +ᨒ +ᨓ +ᨔ +ᨕ +ᨖ +ᬅ +ᬆ +ᬇ +ᬈ +ᬉ +ᬊ +ᬋ +ᬌ +ᬍ +ᬎ +ᬏ +ᬐ +ᬑ +ᬒ +ᬓ +ᬔ +ᬕ +ᬖ +ᬗ +ᬘ +ᬙ +ᬚ +ᬛ +ᬜ +ᬝ +ᬞ +ᬟ +ᬠ +ᬡ +ᬢ +ᬣ +ᬤ +ᬥ +ᬦ +ᬧ +ᬨ +ᬩ +ᬪ +ᬫ +ᬬ +ᬭ +ᬮ +ᬯ +ᬰ +ᬱ +ᬲ +ᬳ +ᭅ +ᭆ +ᭇ +ᭈ +ᭉ +ᭊ +ᭋ +ᮃ +ᮄ +ᮅ +ᮆ +ᮇ +ᮈ +ᮉ +ᮊ +ᮋ +ᮌ +ᮍ +ᮎ +ᮏ +ᮐ +ᮑ +ᮒ +ᮓ +ᮔ +ᮕ +ᮖ +ᮗ +ᮘ +ᮙ +ᮚ +ᮛ +ᮜ +ᮝ +ᮞ +ᮟ +ᮠ +ᮮ +ᮯ +ᰀ +ᰁ +ᰂ +ᰃ +ᰄ +ᰅ +ᰆ +ᰇ +ᰈ +ᰉ +ᰊ +ᰋ +ᰌ +ᰍ +ᰎ +ᰏ +ᰐ +ᰑ +ᰒ +ᰓ +ᰔ +ᰕ +ᰖ +ᰗ +ᰘ +ᰙ +ᰚ +ᰛ +ᰜ +ᰝ +ᰞ +ᰟ +ᰠ +ᰡ +ᰢ +ᰣ +ᱍ +ᱎ +ᱏ +ᱚ +ᱛ +ᱜ +ᱝ +ᱞ +ᱟ +ᱠ +ᱡ +ᱢ +ᱣ +ᱤ +ᱥ +ᱦ +ᱧ +ᱨ +ᱩ +ᱪ +ᱫ +ᱬ +ᱭ +ᱮ +ᱯ +ᱰ +ᱱ +ᱲ +ᱳ +ᱴ +ᱵ +ᱶ +ᱷ +ᴀ +ᴁ +ᴂ +ᴃ +ᴄ +ᴅ +ᴆ +ᴇ +ᴈ +ᴉ +ᴊ +ᴋ +ᴌ +ᴍ +ᴎ +ᴏ +ᴐ +ᴑ +ᴒ +ᴓ +ᴔ +ᴕ +ᴖ +ᴗ +ᴘ +ᴙ +ᴚ +ᴛ +ᴜ +ᴝ +ᴞ +ᴟ +ᴠ +ᴡ +ᴢ +ᴣ +ᴤ +ᴥ +ᴦ +ᴧ +ᴨ +ᴩ +ᴪ +ᴫ +ᵢ +ᵣ +ᵤ +ᵥ +ᵦ +ᵧ +ᵨ +ᵩ +ᵪ +ᵫ +ᵬ +ᵭ +ᵮ +ᵯ +ᵰ +ᵱ +ᵲ +ᵳ +ᵴ +ᵵ +ᵶ +ᵷ +ᵹꝽ +ᵺ +ᵻ +ᵼ +ᵽⱣ +ᵾ +ᵿ +ᶀ +ᶁ +ᶂ +ᶃ +ᶄ +ᶅ +ᶆ +ᶇ +ᶈ +ᶉ +ᶊ +ᶋ +ᶌ +ᶍ +ᶎ +ᶏ +ᶐ +ᶑ +ᶒ +ᶓ +ᶔ +ᶕ +ᶖ +ᶗ +ᶘ +ᶙ +ᶚ +ḁḀ +ḃḂ +ḅḄ +ḇḆ +ḉḈ +ḋḊ +ḍḌ +ḏḎ +ḑḐ +ḓḒ +ḕḔ +ḗḖ +ḙḘ +ḛḚ +ḝḜ +ḟḞ +ḡḠ +ḣḢ +ḥḤ +ḧḦ +ḩḨ +ḫḪ +ḭḬ +ḯḮ +ḱḰ +ḳḲ +ḵḴ +ḷḶ +ḹḸ +ḻḺ +ḽḼ +ḿḾ +ṁṀ +ṃṂ +ṅṄ +ṇṆ +ṉṈ +ṋṊ +ṍṌ +ṏṎ +ṑṐ +ṓṒ +ṕṔ +ṗṖ +ṙṘ +ṛṚ +ṝṜ +ṟṞ +ṡṠ +ṣṢ +ṥṤ +ṧṦ +ṩṨ +ṫṪ +ṭṬ +ṯṮ +ṱṰ +ṳṲ +ṵṴ +ṷṶ +ṹṸ +ṻṺ +ṽṼ +ṿṾ +ẁẀ +ẃẂ +ẅẄ +ẇẆ +ẉẈ +ẋẊ +ẍẌ +ẏẎ +ẑẐ +ẓẒ +ẕẔ +ẖ +ẗ +ẘ +ẙ +ẚ +ẛṠ +ẜ +ẝ +ẟ +ạẠ +ảẢ +ấẤ +ầẦ +ẩẨ +ẫẪ +ậẬ +ắẮ +ằẰ +ẳẲ +ẵẴ +ặẶ +ẹẸ +ẻẺ +ẽẼ +ếẾ +ềỀ +ểỂ +ễỄ +ệỆ +ỉỈ +ịỊ +ọỌ +ỏỎ +ốỐ +ồỒ +ổỔ +ỗỖ +ộỘ +ớỚ +ờỜ +ởỞ +ỡỠ +ợỢ +ụỤ +ủỦ +ứỨ +ừỪ +ửỬ +ữỮ +ựỰ +ỳỲ +ỵỴ +ỷỶ +ỹỸ +ỻỺ +ỽỼ +ỿỾ +ἀἈ +ἁἉ +ἂἊ +ἃἋ +ἄἌ +ἅἍ +ἆἎ +ἇἏ +ἐἘ +ἑἙ +ἒἚ +ἓἛ +ἔἜ +ἕἝ +ἠἨ +ἡἩ +ἢἪ +ἣἫ +ἤἬ +ἥἭ +ἦἮ +ἧἯ +ἰἸ +ἱἹ +ἲἺ +ἳἻ +ἴἼ +ἵἽ +ἶἾ +ἷἿ +ὀὈ +ὁὉ +ὂὊ +ὃὋ +ὄὌ +ὅὍ +ὐ +ὑὙ +ὒ +ὓὛ +ὔ +ὕὝ +ὖ +ὗὟ +ὠὨ +ὡὩ +ὢὪ +ὣὫ +ὤὬ +ὥὭ +ὦὮ +ὧὯ +ὰᾺ +άΆ +ὲῈ +έΈ +ὴῊ +ήΉ +ὶῚ +ίΊ +ὸῸ +όΌ +ὺῪ +ύΎ +ὼῺ +ώΏ +ᾀᾈ +ᾁᾉ +ᾂᾊ +ᾃᾋ +ᾄᾌ +ᾅᾍ +ᾆᾎ +ᾇᾏ +ᾐᾘ +ᾑᾙ +ᾒᾚ +ᾓᾛ +ᾔᾜ +ᾕᾝ +ᾖᾞ +ᾗᾟ +ᾠᾨ +ᾡᾩ +ᾢᾪ +ᾣᾫ +ᾤᾬ +ᾥᾭ +ᾦᾮ +ᾧᾯ +ᾰᾸ +ᾱᾹ +ᾲ +ᾳᾼ +ᾴ +ᾶ +ᾷ +ιΙ +ῂ +ῃῌ +ῄ +ῆ +ῇ +ῐῘ +ῑῙ +ῒ +ΐ +ῖ +ῗ +ῠῨ +ῡῩ +ῢ +ΰ +ῤ +ῥῬ +ῦ +ῧ +ῲ +ῳῼ +ῴ +ῶ +ῷ +ↄↃ +ⰰⰀ +ⰱⰁ +ⰲⰂ +ⰳⰃ +ⰴⰄ +ⰵⰅ +ⰶⰆ +ⰷⰇ +ⰸⰈ +ⰹⰉ +ⰺⰊ +ⰻⰋ +ⰼⰌ +ⰽⰍ +ⰾⰎ +ⰿⰏ +ⱀⰐ +ⱁⰑ +ⱂⰒ +ⱃⰓ +ⱄⰔ +ⱅⰕ +ⱆⰖ +ⱇⰗ +ⱈⰘ +ⱉⰙ +ⱊⰚ +ⱋⰛ +ⱌⰜ +ⱍⰝ +ⱎⰞ +ⱏⰟ +ⱐⰠ +ⱑⰡ +ⱒⰢ +ⱓⰣ +ⱔⰤ +ⱕⰥ +ⱖⰦ +ⱗⰧ +ⱘⰨ +ⱙⰩ +ⱚⰪ +ⱛⰫ +ⱜⰬ +ⱝⰭ +ⱞⰮ +ⱡⱠ +ⱥȺ +ⱦȾ +ⱨⱧ +ⱪⱩ +ⱬⱫ +ⱱ +ⱳⱲ +ⱴ +ⱶⱵ +ⱷ +ⱸ +ⱹ +ⱺ +ⱻ +ⱼ +ⲁⲀ +ⲃⲂ +ⲅⲄ +ⲇⲆ +ⲉⲈ +ⲋⲊ +ⲍⲌ +ⲏⲎ +ⲑⲐ +ⲓⲒ +ⲕⲔ +ⲗⲖ +ⲙⲘ +ⲛⲚ +ⲝⲜ +ⲟⲞ +ⲡⲠ +ⲣⲢ +ⲥⲤ +ⲧⲦ +ⲩⲨ +ⲫⲪ +ⲭⲬ +ⲯⲮ +ⲱⲰ +ⲳⲲ +ⲵⲴ +ⲷⲶ +ⲹⲸ +ⲻⲺ +ⲽⲼ +ⲿⲾ +ⳁⳀ +ⳃⳂ +ⳅⳄ +ⳇⳆ +ⳉⳈ +ⳋⳊ +ⳍⳌ +ⳏⳎ +ⳑⳐ +ⳓⳒ +ⳕⳔ +ⳗⳖ +ⳙⳘ +ⳛⳚ +ⳝⳜ +ⳟⳞ +ⳡⳠ +ⳣⳢ +ⳤ +ⴀႠ +ⴁႡ +ⴂႢ +ⴃႣ +ⴄႤ +ⴅႥ +ⴆႦ +ⴇႧ +ⴈႨ +ⴉႩ +ⴊႪ +ⴋႫ +ⴌႬ +ⴍႭ +ⴎႮ +ⴏႯ +ⴐႰ +ⴑႱ +ⴒႲ +ⴓႳ +ⴔႴ +ⴕႵ +ⴖႶ +ⴗႷ +ⴘႸ +ⴙႹ +ⴚႺ +ⴛႻ +ⴜႼ +ⴝႽ +ⴞႾ +ⴟႿ +ⴠჀ +ⴡჁ +ⴢჂ +ⴣჃ +ⴤჄ +ⴥჅ +ⴰ +ⴱ +ⴲ +ⴳ +ⴴ +ⴵ +ⴶ +ⴷ +ⴸ +ⴹ +ⴺ +ⴻ +ⴼ +ⴽ +ⴾ +ⴿ +ⵀ +ⵁ +ⵂ +ⵃ +ⵄ +ⵅ +ⵆ +ⵇ +ⵈ +ⵉ +ⵊ +ⵋ +ⵌ +ⵍ +ⵎ +ⵏ +ⵐ +ⵑ +ⵒ +ⵓ +ⵔ +ⵕ +ⵖ +ⵗ +ⵘ +ⵙ +ⵚ +ⵛ +ⵜ +ⵝ +ⵞ +ⵟ +ⵠ +ⵡ +ⵢ +ⵣ +ⵤ +ⵥ +ⶀ +ⶁ +ⶂ +ⶃ +ⶄ +ⶅ +ⶆ +ⶇ +ⶈ +ⶉ +ⶊ +ⶋ +ⶌ +ⶍ +ⶎ +ⶏ +ⶐ +ⶑ +ⶒ +ⶓ +ⶔ +ⶕ +ⶖ +ⶠ +ⶡ +ⶢ +ⶣ +ⶤ +ⶥ +ⶦ +ⶨ +ⶩ +ⶪ +ⶫ +ⶬ +ⶭ +ⶮ +ⶰ +ⶱ +ⶲ +ⶳ +ⶴ +ⶵ +ⶶ +ⶸ +ⶹ +ⶺ +ⶻ +ⶼ +ⶽ +ⶾ +ⷀ +ⷁ +ⷂ +ⷃ +ⷄ +ⷅ +ⷆ +ⷈ +ⷉ +ⷊ +ⷋ +ⷌ +ⷍ +ⷎ +ⷐ +ⷑ +ⷒ +ⷓ +ⷔ +ⷕ +ⷖ +ⷘ +ⷙ +ⷚ +ⷛ +ⷜ +ⷝ +ⷞ +〆 +〼 +ぁ +あ +ぃ +い +ぅ +う +ぇ +え +ぉ +お +か +が +き +ぎ +く +ぐ +け +げ +こ +ご +さ +ざ +し +じ +す +ず +せ +ぜ +そ +ぞ +た +だ +ち +ぢ +っ +つ +づ +て +で +と +ど +な +に +ぬ +ね +の +は +ば +ぱ +ひ +び +ぴ +ふ +ぶ +ぷ +へ +べ +ぺ +ほ +ぼ +ぽ +ま +み +む +め +も +ゃ +や +ゅ +ゆ +ょ +よ +ら +り +る +れ +ろ +ゎ +わ +ゐ +ゑ +を +ん +ゔ +ゕ +ゖ +ゟ +ァ +ア +ィ +イ +ゥ +ウ +ェ +エ +ォ +オ +カ +ガ +キ +ギ +ク +グ +ケ +ゲ +コ +ゴ +サ +ザ +シ +ジ +ス +ズ +セ +ゼ +ソ +ゾ +タ +ダ +チ +ヂ +ッ +ツ +ヅ +テ +デ +ト +ド +ナ +ニ +ヌ +ネ +ノ +ハ +バ +パ +ヒ +ビ +ピ +フ +ブ +プ +ヘ +ベ +ペ +ホ +ボ +ポ +マ +ミ +ム +メ +モ +ャ +ヤ +ュ +ユ +ョ +ヨ +ラ +リ +ル +レ +ロ +ヮ +ワ +ヰ +ヱ +ヲ +ン +ヴ +ヵ +ヶ +ヷ +ヸ +ヹ +ヺ +ヿ +ㄅ +ㄆ +ㄇ +ㄈ +ㄉ +ㄊ +ㄋ +ㄌ +ㄍ +ㄎ +ㄏ +ㄐ +ㄑ +ㄒ +ㄓ +ㄔ +ㄕ +ㄖ +ㄗ +ㄘ +ㄙ +ㄚ +ㄛ +ㄜ +ㄝ +ㄞ +ㄟ +ㄠ +ㄡ +ㄢ +ㄣ +ㄤ +ㄥ +ㄦ +ㄧ +ㄨ +ㄩ +ㄪ +ㄫ +ㄬ +ㄭ +ㄱ +ㄲ +ㄳ +ㄴ +ㄵ +ㄶ +ㄷ +ㄸ +ㄹ +ㄺ +ㄻ +ㄼ +ㄽ +ㄾ +ㄿ +ㅀ +ㅁ +ㅂ +ㅃ +ㅄ +ㅅ +ㅆ +ㅇ +ㅈ +ㅉ +ㅊ +ㅋ +ㅌ +ㅍ +ㅎ +ㅏ +ㅐ +ㅑ +ㅒ +ㅓ +ㅔ +ㅕ +ㅖ +ㅗ +ㅘ +ㅙ +ㅚ +ㅛ +ㅜ +ㅝ +ㅞ +ㅟ +ㅠ +ㅡ +ㅢ +ㅣ +ㅤ +ㅥ +ㅦ +ㅧ +ㅨ +ㅩ +ㅪ +ㅫ +ㅬ +ㅭ +ㅮ +ㅯ +ㅰ +ㅱ +ㅲ +ㅳ +ㅴ +ㅵ +ㅶ +ㅷ +ㅸ +ㅹ +ㅺ +ㅻ +ㅼ +ㅽ +ㅾ +ㅿ +ㆀ +ㆁ +ㆂ +ㆃ +ㆄ +ㆅ +ㆆ +ㆇ +ㆈ +ㆉ +ㆊ +ㆋ +ㆌ +ㆍ +ㆎ +ㆠ +ㆡ +ㆢ +ㆣ +ㆤ +ㆥ +ㆦ +ㆧ +ㆨ +ㆩ +ㆪ +ㆫ +ㆬ +ㆭ +ㆮ +ㆯ +ㆰ +ㆱ +ㆲ +ㆳ +ㆴ +ㆵ +ㆶ +ㆷ +ㇰ +ㇱ +ㇲ +ㇳ +ㇴ +ㇵ +ㇶ +ㇷ +ㇸ +ㇹ +ㇺ +ㇻ +ㇼ +ㇽ +ㇾ +ㇿ +ꀀ +ꀁ +ꀂ +ꀃ +ꀄ +ꀅ +ꀆ +ꀇ +ꀈ +ꀉ +ꀊ +ꀋ +ꀌ +ꀍ +ꀎ +ꀏ +ꀐ +ꀑ +ꀒ +ꀓ +ꀔ +ꀖ +ꀗ +ꀘ +ꀙ +ꀚ +ꀛ +ꀜ +ꀝ +ꀞ +ꀟ +ꀠ +ꀡ +ꀢ +ꀣ +ꀤ +ꀥ +ꀦ +ꀧ +ꀨ +ꀩ +ꀪ +ꀫ +ꀬ +ꀭ +ꀮ +ꀯ +ꀰ +ꀱ +ꀲ +ꀳ +ꀴ +ꀵ +ꀶ +ꀷ +ꀸ +ꀹ +ꀺ +ꀻ +ꀼ +ꀽ +ꀾ +ꀿ +ꁀ +ꁁ +ꁂ +ꁃ +ꁄ +ꁅ +ꁆ +ꁇ +ꁈ +ꁉ +ꁊ +ꁋ +ꁌ +ꁍ +ꁎ +ꁏ +ꁐ +ꁑ +ꁒ +ꁓ +ꁔ +ꁕ +ꁖ +ꁗ +ꁘ +ꁙ +ꁚ +ꁛ +ꁜ +ꁝ +ꁞ +ꁟ +ꁠ +ꁡ +ꁢ +ꁣ +ꁤ +ꁥ +ꁦ +ꁧ +ꁨ +ꁩ +ꁪ +ꁫ +ꁬ +ꁭ +ꁮ +ꁯ +ꁰ +ꁱ +ꁲ +ꁳ +ꁴ +ꁵ +ꁶ +ꁷ +ꁸ +ꁹ +ꁺ +ꁻ +ꁼ +ꁽ +ꁾ +ꁿ +ꂀ +ꂁ +ꂂ +ꂃ +ꂄ +ꂅ +ꂆ +ꂇ +ꂈ +ꂉ +ꂊ +ꂋ +ꂌ +ꂍ +ꂎ +ꂏ +ꂐ +ꂑ +ꂒ +ꂓ +ꂔ +ꂕ +ꂖ +ꂗ +ꂘ +ꂙ +ꂚ +ꂛ +ꂜ +ꂝ +ꂞ +ꂟ +ꂠ +ꂡ +ꂢ +ꂣ +ꂤ +ꂥ +ꂦ +ꂧ +ꂨ +ꂩ +ꂪ +ꂫ +ꂬ +ꂭ +ꂮ +ꂯ +ꂰ +ꂱ +ꂲ +ꂳ +ꂴ +ꂵ +ꂶ +ꂷ +ꂸ +ꂹ +ꂺ +ꂻ +ꂼ +ꂽ +ꂾ +ꂿ +ꃀ +ꃁ +ꃂ +ꃃ +ꃄ +ꃅ +ꃆ +ꃇ +ꃈ +ꃉ +ꃊ +ꃋ +ꃌ +ꃍ +ꃎ +ꃏ +ꃐ +ꃑ +ꃒ +ꃓ +ꃔ +ꃕ +ꃖ +ꃗ +ꃘ +ꃙ +ꃚ +ꃛ +ꃜ +ꃝ +ꃞ +ꃟ +ꃠ +ꃡ +ꃢ +ꃣ +ꃤ +ꃥ +ꃦ +ꃧ +ꃨ +ꃩ +ꃪ +ꃫ +ꃬ +ꃭ +ꃮ +ꃯ +ꃰ +ꃱ +ꃲ +ꃳ +ꃴ +ꃵ +ꃶ +ꃷ +ꃸ +ꃹ +ꃺ +ꃻ +ꃼ +ꃽ +ꃾ +ꃿ +ꄀ +ꄁ +ꄂ +ꄃ +ꄄ +ꄅ +ꄆ +ꄇ +ꄈ +ꄉ +ꄊ +ꄋ +ꄌ +ꄍ +ꄎ +ꄏ +ꄐ +ꄑ +ꄒ +ꄓ +ꄔ +ꄕ +ꄖ +ꄗ +ꄘ +ꄙ +ꄚ +ꄛ +ꄜ +ꄝ +ꄞ +ꄟ +ꄠ +ꄡ +ꄢ +ꄣ +ꄤ +ꄥ +ꄦ +ꄧ +ꄨ +ꄩ +ꄪ +ꄫ +ꄬ +ꄭ +ꄮ +ꄯ +ꄰ +ꄱ +ꄲ +ꄳ +ꄴ +ꄵ +ꄶ +ꄷ +ꄸ +ꄹ +ꄺ +ꄻ +ꄼ +ꄽ +ꄾ +ꄿ +ꅀ +ꅁ +ꅂ +ꅃ +ꅄ +ꅅ +ꅆ +ꅇ +ꅈ +ꅉ +ꅊ +ꅋ +ꅌ +ꅍ +ꅎ +ꅏ +ꅐ +ꅑ +ꅒ +ꅓ +ꅔ +ꅕ +ꅖ +ꅗ +ꅘ +ꅙ +ꅚ +ꅛ +ꅜ +ꅝ +ꅞ +ꅟ +ꅠ +ꅡ +ꅢ +ꅣ +ꅤ +ꅥ +ꅦ +ꅧ +ꅨ +ꅩ +ꅪ +ꅫ +ꅬ +ꅭ +ꅮ +ꅯ +ꅰ +ꅱ +ꅲ +ꅳ +ꅴ +ꅵ +ꅶ +ꅷ +ꅸ +ꅹ +ꅺ +ꅻ +ꅼ +ꅽ +ꅾ +ꅿ +ꆀ +ꆁ +ꆂ +ꆃ +ꆄ +ꆅ +ꆆ +ꆇ +ꆈ +ꆉ +ꆊ +ꆋ +ꆌ +ꆍ +ꆎ +ꆏ +ꆐ +ꆑ +ꆒ +ꆓ +ꆔ +ꆕ +ꆖ +ꆗ +ꆘ +ꆙ +ꆚ +ꆛ +ꆜ +ꆝ +ꆞ +ꆟ +ꆠ +ꆡ +ꆢ +ꆣ +ꆤ +ꆥ +ꆦ +ꆧ +ꆨ +ꆩ +ꆪ +ꆫ +ꆬ +ꆭ +ꆮ +ꆯ +ꆰ +ꆱ +ꆲ +ꆳ +ꆴ +ꆵ +ꆶ +ꆷ +ꆸ +ꆹ +ꆺ +ꆻ +ꆼ +ꆽ +ꆾ +ꆿ +ꇀ +ꇁ +ꇂ +ꇃ +ꇄ +ꇅ +ꇆ +ꇇ +ꇈ +ꇉ +ꇊ +ꇋ +ꇌ +ꇍ +ꇎ +ꇏ +ꇐ +ꇑ +ꇒ +ꇓ +ꇔ +ꇕ +ꇖ +ꇗ +ꇘ +ꇙ +ꇚ +ꇛ +ꇜ +ꇝ +ꇞ +ꇟ +ꇠ +ꇡ +ꇢ +ꇣ +ꇤ +ꇥ +ꇦ +ꇧ +ꇨ +ꇩ +ꇪ +ꇫ +ꇬ +ꇭ +ꇮ +ꇯ +ꇰ +ꇱ +ꇲ +ꇳ +ꇴ +ꇵ +ꇶ +ꇷ +ꇸ +ꇹ +ꇺ +ꇻ +ꇼ +ꇽ +ꇾ +ꇿ +ꈀ +ꈁ +ꈂ +ꈃ +ꈄ +ꈅ +ꈆ +ꈇ +ꈈ +ꈉ +ꈊ +ꈋ +ꈌ +ꈍ +ꈎ +ꈏ +ꈐ +ꈑ +ꈒ +ꈓ +ꈔ +ꈕ +ꈖ +ꈗ +ꈘ +ꈙ +ꈚ +ꈛ +ꈜ +ꈝ +ꈞ +ꈟ +ꈠ +ꈡ +ꈢ +ꈣ +ꈤ +ꈥ +ꈦ +ꈧ +ꈨ +ꈩ +ꈪ +ꈫ +ꈬ +ꈭ +ꈮ +ꈯ +ꈰ +ꈱ +ꈲ +ꈳ +ꈴ +ꈵ +ꈶ +ꈷ +ꈸ +ꈹ +ꈺ +ꈻ +ꈼ +ꈽ +ꈾ +ꈿ +ꉀ +ꉁ +ꉂ +ꉃ +ꉄ +ꉅ +ꉆ +ꉇ +ꉈ +ꉉ +ꉊ +ꉋ +ꉌ +ꉍ +ꉎ +ꉏ +ꉐ +ꉑ +ꉒ +ꉓ +ꉔ +ꉕ +ꉖ +ꉗ +ꉘ +ꉙ +ꉚ +ꉛ +ꉜ +ꉝ +ꉞ +ꉟ +ꉠ +ꉡ +ꉢ +ꉣ +ꉤ +ꉥ +ꉦ +ꉧ +ꉨ +ꉩ +ꉪ +ꉫ +ꉬ +ꉭ +ꉮ +ꉯ +ꉰ +ꉱ +ꉲ +ꉳ +ꉴ +ꉵ +ꉶ +ꉷ +ꉸ +ꉹ +ꉺ +ꉻ +ꉼ +ꉽ +ꉾ +ꉿ +ꊀ +ꊁ +ꊂ +ꊃ +ꊄ +ꊅ +ꊆ +ꊇ +ꊈ +ꊉ +ꊊ +ꊋ +ꊌ +ꊍ +ꊎ +ꊏ +ꊐ +ꊑ +ꊒ +ꊓ +ꊔ +ꊕ +ꊖ +ꊗ +ꊘ +ꊙ +ꊚ +ꊛ +ꊜ +ꊝ +ꊞ +ꊟ +ꊠ +ꊡ +ꊢ +ꊣ +ꊤ +ꊥ +ꊦ +ꊧ +ꊨ +ꊩ +ꊪ +ꊫ +ꊬ +ꊭ +ꊮ +ꊯ +ꊰ +ꊱ +ꊲ +ꊳ +ꊴ +ꊵ +ꊶ +ꊷ +ꊸ +ꊹ +ꊺ +ꊻ +ꊼ +ꊽ +ꊾ +ꊿ +ꋀ +ꋁ +ꋂ +ꋃ +ꋄ +ꋅ +ꋆ +ꋇ +ꋈ +ꋉ +ꋊ +ꋋ +ꋌ +ꋍ +ꋎ +ꋏ +ꋐ +ꋑ +ꋒ +ꋓ +ꋔ +ꋕ +ꋖ +ꋗ +ꋘ +ꋙ +ꋚ +ꋛ +ꋜ +ꋝ +ꋞ +ꋟ +ꋠ +ꋡ +ꋢ +ꋣ +ꋤ +ꋥ +ꋦ +ꋧ +ꋨ +ꋩ +ꋪ +ꋫ +ꋬ +ꋭ +ꋮ +ꋯ +ꋰ +ꋱ +ꋲ +ꋳ +ꋴ +ꋵ +ꋶ +ꋷ +ꋸ +ꋹ +ꋺ +ꋻ +ꋼ +ꋽ +ꋾ +ꋿ +ꌀ +ꌁ +ꌂ +ꌃ +ꌄ +ꌅ +ꌆ +ꌇ +ꌈ +ꌉ +ꌊ +ꌋ +ꌌ +ꌍ +ꌎ +ꌏ +ꌐ +ꌑ +ꌒ +ꌓ +ꌔ +ꌕ +ꌖ +ꌗ +ꌘ +ꌙ +ꌚ +ꌛ +ꌜ +ꌝ +ꌞ +ꌟ +ꌠ +ꌡ +ꌢ +ꌣ +ꌤ +ꌥ +ꌦ +ꌧ +ꌨ +ꌩ +ꌪ +ꌫ +ꌬ +ꌭ +ꌮ +ꌯ +ꌰ +ꌱ +ꌲ +ꌳ +ꌴ +ꌵ +ꌶ +ꌷ +ꌸ +ꌹ +ꌺ +ꌻ +ꌼ +ꌽ +ꌾ +ꌿ +ꍀ +ꍁ +ꍂ +ꍃ +ꍄ +ꍅ +ꍆ +ꍇ +ꍈ +ꍉ +ꍊ +ꍋ +ꍌ +ꍍ +ꍎ +ꍏ +ꍐ +ꍑ +ꍒ +ꍓ +ꍔ +ꍕ +ꍖ +ꍗ +ꍘ +ꍙ +ꍚ +ꍛ +ꍜ +ꍝ +ꍞ +ꍟ +ꍠ +ꍡ +ꍢ +ꍣ +ꍤ +ꍥ +ꍦ +ꍧ +ꍨ +ꍩ +ꍪ +ꍫ +ꍬ +ꍭ +ꍮ +ꍯ +ꍰ +ꍱ +ꍲ +ꍳ +ꍴ +ꍵ +ꍶ +ꍷ +ꍸ +ꍹ +ꍺ +ꍻ +ꍼ +ꍽ +ꍾ +ꍿ +ꎀ +ꎁ +ꎂ +ꎃ +ꎄ +ꎅ +ꎆ +ꎇ +ꎈ +ꎉ +ꎊ +ꎋ +ꎌ +ꎍ +ꎎ +ꎏ +ꎐ +ꎑ +ꎒ +ꎓ +ꎔ +ꎕ +ꎖ +ꎗ +ꎘ +ꎙ +ꎚ +ꎛ +ꎜ +ꎝ +ꎞ +ꎟ +ꎠ +ꎡ +ꎢ +ꎣ +ꎤ +ꎥ +ꎦ +ꎧ +ꎨ +ꎩ +ꎪ +ꎫ +ꎬ +ꎭ +ꎮ +ꎯ +ꎰ +ꎱ +ꎲ +ꎳ +ꎴ +ꎵ +ꎶ +ꎷ +ꎸ +ꎹ +ꎺ +ꎻ +ꎼ +ꎽ +ꎾ +ꎿ +ꏀ +ꏁ +ꏂ +ꏃ +ꏄ +ꏅ +ꏆ +ꏇ +ꏈ +ꏉ +ꏊ +ꏋ +ꏌ +ꏍ +ꏎ +ꏏ +ꏐ +ꏑ +ꏒ +ꏓ +ꏔ +ꏕ +ꏖ +ꏗ +ꏘ +ꏙ +ꏚ +ꏛ +ꏜ +ꏝ +ꏞ +ꏟ +ꏠ +ꏡ +ꏢ +ꏣ +ꏤ +ꏥ +ꏦ +ꏧ +ꏨ +ꏩ +ꏪ +ꏫ +ꏬ +ꏭ +ꏮ +ꏯ +ꏰ +ꏱ +ꏲ +ꏳ +ꏴ +ꏵ +ꏶ +ꏷ +ꏸ +ꏹ +ꏺ +ꏻ +ꏼ +ꏽ +ꏾ +ꏿ +ꐀ +ꐁ +ꐂ +ꐃ +ꐄ +ꐅ +ꐆ +ꐇ +ꐈ +ꐉ +ꐊ +ꐋ +ꐌ +ꐍ +ꐎ +ꐏ +ꐐ +ꐑ +ꐒ +ꐓ +ꐔ +ꐕ +ꐖ +ꐗ +ꐘ +ꐙ +ꐚ +ꐛ +ꐜ +ꐝ +ꐞ +ꐟ +ꐠ +ꐡ +ꐢ +ꐣ +ꐤ +ꐥ +ꐦ +ꐧ +ꐨ +ꐩ +ꐪ +ꐫ +ꐬ +ꐭ +ꐮ +ꐯ +ꐰ +ꐱ +ꐲ +ꐳ +ꐴ +ꐵ +ꐶ +ꐷ +ꐸ +ꐹ +ꐺ +ꐻ +ꐼ +ꐽ +ꐾ +ꐿ +ꑀ +ꑁ +ꑂ +ꑃ +ꑄ +ꑅ +ꑆ +ꑇ +ꑈ +ꑉ +ꑊ +ꑋ +ꑌ +ꑍ +ꑎ +ꑏ +ꑐ +ꑑ +ꑒ +ꑓ +ꑔ +ꑕ +ꑖ +ꑗ +ꑘ +ꑙ +ꑚ +ꑛ +ꑜ +ꑝ +ꑞ +ꑟ +ꑠ +ꑡ +ꑢ +ꑣ +ꑤ +ꑥ +ꑦ +ꑧ +ꑨ +ꑩ +ꑪ +ꑫ +ꑬ +ꑭ +ꑮ +ꑯ +ꑰ +ꑱ +ꑲ +ꑳ +ꑴ +ꑵ +ꑶ +ꑷ +ꑸ +ꑹ +ꑺ +ꑻ +ꑼ +ꑽ +ꑾ +ꑿ +ꒀ +ꒁ +ꒂ +ꒃ +ꒄ +ꒅ +ꒆ +ꒇ +ꒈ +ꒉ +ꒊ +ꒋ +ꒌ +ꔀ +ꔁ +ꔂ +ꔃ +ꔄ +ꔅ +ꔆ +ꔇ +ꔈ +ꔉ +ꔊ +ꔋ +ꔌ +ꔍ +ꔎ +ꔏ +ꔐ +ꔑ +ꔒ +ꔓ +ꔔ +ꔕ +ꔖ +ꔗ +ꔘ +ꔙ +ꔚ +ꔛ +ꔜ +ꔝ +ꔞ +ꔟ +ꔠ +ꔡ +ꔢ +ꔣ +ꔤ +ꔥ +ꔦ +ꔧ +ꔨ +ꔩ +ꔪ +ꔫ +ꔬ +ꔭ +ꔮ +ꔯ +ꔰ +ꔱ +ꔲ +ꔳ +ꔴ +ꔵ +ꔶ +ꔷ +ꔸ +ꔹ +ꔺ +ꔻ +ꔼ +ꔽ +ꔾ +ꔿ +ꕀ +ꕁ +ꕂ +ꕃ +ꕄ +ꕅ +ꕆ +ꕇ +ꕈ +ꕉ +ꕊ +ꕋ +ꕌ +ꕍ +ꕎ +ꕏ +ꕐ +ꕑ +ꕒ +ꕓ +ꕔ +ꕕ +ꕖ +ꕗ +ꕘ +ꕙ +ꕚ +ꕛ +ꕜ +ꕝ +ꕞ +ꕟ +ꕠ +ꕡ +ꕢ +ꕣ +ꕤ +ꕥ +ꕦ +ꕧ +ꕨ +ꕩ +ꕪ +ꕫ +ꕬ +ꕭ +ꕮ +ꕯ +ꕰ +ꕱ +ꕲ +ꕳ +ꕴ +ꕵ +ꕶ +ꕷ +ꕸ +ꕹ +ꕺ +ꕻ +ꕼ +ꕽ +ꕾ +ꕿ +ꖀ +ꖁ +ꖂ +ꖃ +ꖄ +ꖅ +ꖆ +ꖇ +ꖈ +ꖉ +ꖊ +ꖋ +ꖌ +ꖍ +ꖎ +ꖏ +ꖐ +ꖑ +ꖒ +ꖓ +ꖔ +ꖕ +ꖖ +ꖗ +ꖘ +ꖙ +ꖚ +ꖛ +ꖜ +ꖝ +ꖞ +ꖟ +ꖠ +ꖡ +ꖢ +ꖣ +ꖤ +ꖥ +ꖦ +ꖧ +ꖨ +ꖩ +ꖪ +ꖫ +ꖬ +ꖭ +ꖮ +ꖯ +ꖰ +ꖱ +ꖲ +ꖳ +ꖴ +ꖵ +ꖶ +ꖷ +ꖸ +ꖹ +ꖺ +ꖻ +ꖼ +ꖽ +ꖾ +ꖿ +ꗀ +ꗁ +ꗂ +ꗃ +ꗄ +ꗅ +ꗆ +ꗇ +ꗈ +ꗉ +ꗊ +ꗋ +ꗌ +ꗍ +ꗎ +ꗏ +ꗐ +ꗑ +ꗒ +ꗓ +ꗔ +ꗕ +ꗖ +ꗗ +ꗘ +ꗙ +ꗚ +ꗛ +ꗜ +ꗝ +ꗞ +ꗟ +ꗠ +ꗡ +ꗢ +ꗣ +ꗤ +ꗥ +ꗦ +ꗧ +ꗨ +ꗩ +ꗪ +ꗫ +ꗬ +ꗭ +ꗮ +ꗯ +ꗰ +ꗱ +ꗲ +ꗳ +ꗴ +ꗵ +ꗶ +ꗷ +ꗸ +ꗹ +ꗺ +ꗻ +ꗼ +ꗽ +ꗾ +ꗿ +ꘀ +ꘁ +ꘂ +ꘃ +ꘄ +ꘅ +ꘆ +ꘇ +ꘈ +ꘉ +ꘊ +ꘋ +ꘐ +ꘑ +ꘒ +ꘓ +ꘔ +ꘕ +ꘖ +ꘗ +ꘘ +ꘙ +ꘚ +ꘛ +ꘜ +ꘝ +ꘞ +ꘟ +ꘪ +ꘫ +ꙁꙀ +ꙃꙂ +ꙅꙄ +ꙇꙆ +ꙉꙈ +ꙋꙊ +ꙍꙌ +ꙏꙎ +ꙑꙐ +ꙓꙒ +ꙕꙔ +ꙗꙖ +ꙙꙘ +ꙛꙚ +ꙝꙜ +ꙟꙞ +ꙣꙢ +ꙥꙤ +ꙧꙦ +ꙩꙨ +ꙫꙪ +ꙭꙬ +ꙮ +ꚁꚀ +ꚃꚂ +ꚅꚄ +ꚇꚆ +ꚉꚈ +ꚋꚊ +ꚍꚌ +ꚏꚎ +ꚑꚐ +ꚓꚒ +ꚕꚔ +ꚗꚖ +ꜣꜢ +ꜥꜤ +ꜧꜦ +ꜩꜨ +ꜫꜪ +ꜭꜬ +ꜯꜮ +ꜰ +ꜱ +ꜳꜲ +ꜵꜴ +ꜷꜶ +ꜹꜸ +ꜻꜺ +ꜽꜼ +ꜿꜾ +ꝁꝀ +ꝃꝂ +ꝅꝄ +ꝇꝆ +ꝉꝈ +ꝋꝊ +ꝍꝌ +ꝏꝎ +ꝑꝐ +ꝓꝒ +ꝕꝔ +ꝗꝖ +ꝙꝘ +ꝛꝚ +ꝝꝜ +ꝟꝞ +ꝡꝠ +ꝣꝢ +ꝥꝤ +ꝧꝦ +ꝩꝨ +ꝫꝪ +ꝭꝬ +ꝯꝮ +ꝱ +ꝲ +ꝳ +ꝴ +ꝵ +ꝶ +ꝷ +ꝸ +ꝺꝹ +ꝼꝻ +ꝿꝾ +ꞁꞀ +ꞃꞂ +ꞅꞄ +ꞇꞆ +ꞌꞋ +ꟻ +ꟼ +ꟽ +ꟾ +ꟿ +ꠀ +ꠁ +ꠃ +ꠄ +ꠅ +ꠇ +ꠈ +ꠉ +ꠊ +ꠌ +ꠍ +ꠎ +ꠏ +ꠐ +ꠑ +ꠒ +ꠓ +ꠔ +ꠕ +ꠖ +ꠗ +ꠘ +ꠙ +ꠚ +ꠛ +ꠜ +ꠝ +ꠞ +ꠟ +ꠠ +ꠡ +ꠢ +ꡀ +ꡁ +ꡂ +ꡃ +ꡄ +ꡅ +ꡆ +ꡇ +ꡈ +ꡉ +ꡊ +ꡋ +ꡌ +ꡍ +ꡎ +ꡏ +ꡐ +ꡑ +ꡒ +ꡓ +ꡔ +ꡕ +ꡖ +ꡗ +ꡘ +ꡙ +ꡚ +ꡛ +ꡜ +ꡝ +ꡞ +ꡟ +ꡠ +ꡡ +ꡢ +ꡣ +ꡤ +ꡥ +ꡦ +ꡧ +ꡨ +ꡩ +ꡪ +ꡫ +ꡬ +ꡭ +ꡮ +ꡯ +ꡰ +ꡱ +ꡲ +ꡳ +ꢂ +ꢃ +ꢄ +ꢅ +ꢆ +ꢇ +ꢈ +ꢉ +ꢊ +ꢋ +ꢌ +ꢍ +ꢎ +ꢏ +ꢐ +ꢑ +ꢒ +ꢓ +ꢔ +ꢕ +ꢖ +ꢗ +ꢘ +ꢙ +ꢚ +ꢛ +ꢜ +ꢝ +ꢞ +ꢟ +ꢠ +ꢡ +ꢢ +ꢣ +ꢤ +ꢥ +ꢦ +ꢧ +ꢨ +ꢩ +ꢪ +ꢫ +ꢬ +ꢭ +ꢮ +ꢯ +ꢰ +ꢱ +ꢲ +ꢳ +ꤊ +ꤋ +ꤌ +ꤍ +ꤎ +ꤏ +ꤐ +ꤑ +ꤒ +ꤓ +ꤔ +ꤕ +ꤖ +ꤗ +ꤘ +ꤙ +ꤚ +ꤛ +ꤜ +ꤝ +ꤞ +ꤟ +ꤠ +ꤡ +ꤢ +ꤣ +ꤤ +ꤥ +ꤰ +ꤱ +ꤲ +ꤳ +ꤴ +ꤵ +ꤶ +ꤷ +ꤸ +ꤹ +ꤺ +ꤻ +ꤼ +ꤽ +ꤾ +ꤿ +ꥀ +ꥁ +ꥂ +ꥃ +ꥄ +ꥅ +ꥆ +ꨀ +ꨁ +ꨂ +ꨃ +ꨄ +ꨅ +ꨆ +ꨇ +ꨈ +ꨉ +ꨊ +ꨋ +ꨌ +ꨍ +ꨎ +ꨏ +ꨐ +ꨑ +ꨒ +ꨓ +ꨔ +ꨕ +ꨖ +ꨗ +ꨘ +ꨙ +ꨚ +ꨛ +ꨜ +ꨝ +ꨞ +ꨟ +ꨠ +ꨡ +ꨢ +ꨣ +ꨤ +ꨥ +ꨦ +ꨧ +ꨨ +ꩀ +ꩁ +ꩂ +ꩄ +ꩅ +ꩆ +ꩇ +ꩈ +ꩉ +ꩊ +ꩋ +豈 +更 +車 +賈 +滑 +串 +句 +龜 +龜 +契 +金 +喇 +奈 +懶 +癩 +羅 +蘿 +螺 +裸 +邏 +樂 +洛 +烙 +珞 +落 +酪 +駱 +亂 +卵 +欄 +爛 +蘭 +鸞 +嵐 +濫 +藍 +襤 +拉 +臘 +蠟 +廊 +朗 +浪 +狼 +郎 +來 +冷 +勞 +擄 +櫓 +爐 +盧 +老 +蘆 +虜 +路 +露 +魯 +鷺 +碌 +祿 +綠 +菉 +錄 +鹿 +論 +壟 +弄 +籠 +聾 +牢 +磊 +賂 +雷 +壘 +屢 +樓 +淚 +漏 +累 +縷 +陋 +勒 +肋 +凜 +凌 +稜 +綾 +菱 +陵 +讀 +拏 +樂 +諾 +丹 +寧 +怒 +率 +異 +北 +磻 +便 +復 +不 +泌 +數 +索 +參 +塞 +省 +葉 +說 +殺 +辰 +沈 +拾 +若 +掠 +略 +亮 +兩 +凉 +梁 +糧 +良 +諒 +量 +勵 +呂 +女 +廬 +旅 +濾 +礪 +閭 +驪 +麗 +黎 +力 +曆 +歷 +轢 +年 +憐 +戀 +撚 +漣 +煉 +璉 +秊 +練 +聯 +輦 +蓮 +連 +鍊 +列 +劣 +咽 +烈 +裂 +說 +廉 +念 +捻 +殮 +簾 +獵 +令 +囹 +寧 +嶺 +怜 +玲 +瑩 +羚 +聆 +鈴 +零 +靈 +領 +例 +禮 +醴 +隸 +惡 +了 +僚 +寮 +尿 +料 +樂 +燎 +療 +蓼 +遼 +龍 +暈 +阮 +劉 +杻 +柳 +流 +溜 +琉 +留 +硫 +紐 +類 +六 +戮 +陸 +倫 +崙 +淪 +輪 +律 +慄 +栗 +率 +隆 +利 +吏 +履 +易 +李 +梨 +泥 +理 +痢 +罹 +裏 +裡 +里 +離 +匿 +溺 +吝 +燐 +璘 +藺 +隣 +鱗 +麟 +林 +淋 +臨 +立 +笠 +粒 +狀 +炙 +識 +什 +茶 +刺 +切 +度 +拓 +糖 +宅 +洞 +暴 +輻 +行 +降 +見 +廓 +兀 +嗀 +﨎 +﨏 +塚 +﨑 +晴 +﨓 +﨔 +凞 +猪 +益 +礼 +神 +祥 +福 +靖 +精 +羽 +﨟 +蘒 +﨡 +諸 +﨣 +﨤 +逸 +都 +﨧 +﨨 +﨩 +飯 +飼 +館 +鶴 +侮 +僧 +免 +勉 +勤 +卑 +喝 +嘆 +器 +塀 +墨 +層 +屮 +悔 +慨 +憎 +懲 +敏 +既 +暑 +梅 +海 +渚 +漢 +煮 +爫 +琢 +碑 +社 +祉 +祈 +祐 +祖 +祝 +禍 +禎 +穀 +突 +節 +練 +縉 +繁 +署 +者 +臭 +艹 +艹 +著 +褐 +視 +謁 +謹 +賓 +贈 +辶 +逸 +難 +響 +頻 +並 +况 +全 +侀 +充 +冀 +勇 +勺 +喝 +啕 +喙 +嗢 +塚 +墳 +奄 +奔 +婢 +嬨 +廒 +廙 +彩 +徭 +惘 +慎 +愈 +憎 +慠 +懲 +戴 +揄 +搜 +摒 +敖 +晴 +朗 +望 +杖 +歹 +殺 +流 +滛 +滋 +漢 +瀞 +煮 +瞧 +爵 +犯 +猪 +瑱 +甆 +画 +瘝 +瘟 +益 +盛 +直 +睊 +着 +磌 +窱 +節 +类 +絛 +練 +缾 +者 +荒 +華 +蝹 +襁 +覆 +視 +調 +諸 +請 +謁 +諾 +諭 +謹 +變 +贈 +輸 +遲 +醙 +鉶 +陼 +難 +靖 +韛 +響 +頋 +頻 +鬒 +龜 +𢡊 +𢡄 +𣏕 +㮝 +䀘 +䀹 +𥉉 +𥳐 +𧻓 +齃 +龎 +ﭐ +ﭑ +ﭒ +ﭓ +ﭔ +ﭕ +ﭖ +ﭗ +ﭘ +ﭙ +ﭚ +ﭛ +ﭜ +ﭝ +ﭞ +ﭟ +ﭠ +ﭡ +ﭢ +ﭣ +ﭤ +ﭥ +ﭦ +ﭧ +ﭨ +ﭩ +ﭪ +ﭫ +ﭬ +ﭭ +ﭮ +ﭯ +ﭰ +ﭱ +ﭲ +ﭳ +ﭴ +ﭵ +ﭶ +ﭷ +ﭸ +ﭹ +ﭺ +ﭻ +ﭼ +ﭽ +ﭾ +ﭿ +ﮀ +ﮁ +ﮂ +ﮃ +ﮄ +ﮅ +ﮆ +ﮇ +ﮈ +ﮉ +ﮊ +ﮋ +ﮌ +ﮍ +ﮎ +ﮏ +ﮐ +ﮑ +ﮒ +ﮓ +ﮔ +ﮕ +ﮖ +ﮗ +ﮘ +ﮙ +ﮚ +ﮛ +ﮜ +ﮝ +ﮞ +ﮟ +ﮠ +ﮡ +ﮢ +ﮣ +ﮤ +ﮥ +ﮦ +ﮧ +ﮨ +ﮩ +ﮪ +ﮫ +ﮬ +ﮭ +ﮮ +ﮯ +ﮰ +ﮱ +ﯓ +ﯔ +ﯕ +ﯖ +ﯗ +ﯘ +ﯙ +ﯚ +ﯛ +ﯜ +ﯝ +ﯞ +ﯟ +ﯠ +ﯡ +ﯢ +ﯣ +ﯤ +ﯥ +ﯦ +ﯧ +ﯨ +ﯩ +ﯪ +ﯫ +ﯬ +ﯭ +ﯮ +ﯯ +ﯰ +ﯱ +ﯲ +ﯳ +ﯴ +ﯵ +ﯶ +ﯷ +ﯸ +ﯹ +ﯺ +ﯻ +ﯼ +ﯽ +ﯾ +ﯿ +ﰀ +ﰁ +ﰂ +ﰃ +ﰄ +ﰅ +ﰆ +ﰇ +ﰈ +ﰉ +ﰊ +ﰋ +ﰌ +ﰍ +ﰎ +ﰏ +ﰐ +ﰑ +ﰒ +ﰓ +ﰔ +ﰕ +ﰖ +ﰗ +ﰘ +ﰙ +ﰚ +ﰛ +ﰜ +ﰝ +ﰞ +ﰟ +ﰠ +ﰡ +ﰢ +ﰣ +ﰤ +ﰥ +ﰦ +ﰧ +ﰨ +ﰩ +ﰪ +ﰫ +ﰬ +ﰭ +ﰮ +ﰯ +ﰰ +ﰱ +ﰲ +ﰳ +ﰴ +ﰵ +ﰶ +ﰷ +ﰸ +ﰹ +ﰺ +ﰻ +ﰼ +ﰽ +ﰾ +ﰿ +ﱀ +ﱁ +ﱂ +ﱃ +ﱄ +ﱅ +ﱆ +ﱇ +ﱈ +ﱉ +ﱊ +ﱋ +ﱌ +ﱍ +ﱎ +ﱏ +ﱐ +ﱑ +ﱒ +ﱓ +ﱔ +ﱕ +ﱖ +ﱗ +ﱘ +ﱙ +ﱚ +ﱛ +ﱜ +ﱝ +ﱞ +ﱟ +ﱠ +ﱡ +ﱢ +ﱣ +ﱤ +ﱥ +ﱦ +ﱧ +ﱨ +ﱩ +ﱪ +ﱫ +ﱬ +ﱭ +ﱮ +ﱯ +ﱰ +ﱱ +ﱲ +ﱳ +ﱴ +ﱵ +ﱶ +ﱷ +ﱸ +ﱹ +ﱺ +ﱻ +ﱼ +ﱽ +ﱾ +ﱿ +ﲀ +ﲁ +ﲂ +ﲃ +ﲄ +ﲅ +ﲆ +ﲇ +ﲈ +ﲉ +ﲊ +ﲋ +ﲌ +ﲍ +ﲎ +ﲏ +ﲐ +ﲑ +ﲒ +ﲓ +ﲔ +ﲕ +ﲖ +ﲗ +ﲘ +ﲙ +ﲚ +ﲛ +ﲜ +ﲝ +ﲞ +ﲟ +ﲠ +ﲡ +ﲢ +ﲣ +ﲤ +ﲥ +ﲦ +ﲧ +ﲨ +ﲩ +ﲪ +ﲫ +ﲬ +ﲭ +ﲮ +ﲯ +ﲰ +ﲱ +ﲲ +ﲳ +ﲴ +ﲵ +ﲶ +ﲷ +ﲸ +ﲹ +ﲺ +ﲻ +ﲼ +ﲽ +ﲾ +ﲿ +ﳀ +ﳁ +ﳂ +ﳃ +ﳄ +ﳅ +ﳆ +ﳇ +ﳈ +ﳉ +ﳊ +ﳋ +ﳌ +ﳍ +ﳎ +ﳏ +ﳐ +ﳑ +ﳒ +ﳓ +ﳔ +ﳕ +ﳖ +ﳗ +ﳘ +ﳙ +ﳚ +ﳛ +ﳜ +ﳝ +ﳞ +ﳟ +ﳠ +ﳡ +ﳢ +ﳣ +ﳤ +ﳥ +ﳦ +ﳧ +ﳨ +ﳩ +ﳪ +ﳫ +ﳬ +ﳭ +ﳮ +ﳯ +ﳰ +ﳱ +ﳲ +ﳳ +ﳴ +ﳵ +ﳶ +ﳷ +ﳸ +ﳹ +ﳺ +ﳻ +ﳼ +ﳽ +ﳾ +ﳿ +ﴀ +ﴁ +ﴂ +ﴃ +ﴄ +ﴅ +ﴆ +ﴇ +ﴈ +ﴉ +ﴊ +ﴋ +ﴌ +ﴍ +ﴎ +ﴏ +ﴐ +ﴑ +ﴒ +ﴓ +ﴔ +ﴕ +ﴖ +ﴗ +ﴘ +ﴙ +ﴚ +ﴛ +ﴜ +ﴝ +ﴞ +ﴟ +ﴠ +ﴡ +ﴢ +ﴣ +ﴤ +ﴥ +ﴦ +ﴧ +ﴨ +ﴩ +ﴪ +ﴫ +ﴬ +ﴭ +ﴮ +ﴯ +ﴰ +ﴱ +ﴲ +ﴳ +ﴴ +ﴵ +ﴶ +ﴷ +ﴸ +ﴹ +ﴺ +ﴻ +ﴼ +ﴽ +ﵐ +ﵑ +ﵒ +ﵓ +ﵔ +ﵕ +ﵖ +ﵗ +ﵘ +ﵙ +ﵚ +ﵛ +ﵜ +ﵝ +ﵞ +ﵟ +ﵠ +ﵡ +ﵢ +ﵣ +ﵤ +ﵥ +ﵦ +ﵧ +ﵨ +ﵩ +ﵪ +ﵫ +ﵬ +ﵭ +ﵮ +ﵯ +ﵰ +ﵱ +ﵲ +ﵳ +ﵴ +ﵵ +ﵶ +ﵷ +ﵸ +ﵹ +ﵺ +ﵻ +ﵼ +ﵽ +ﵾ +ﵿ +ﶀ +ﶁ +ﶂ +ﶃ +ﶄ +ﶅ +ﶆ +ﶇ +ﶈ +ﶉ +ﶊ +ﶋ +ﶌ +ﶍ +ﶎ +ﶏ +ﶒ +ﶓ +ﶔ +ﶕ +ﶖ +ﶗ +ﶘ +ﶙ +ﶚ +ﶛ +ﶜ +ﶝ +ﶞ +ﶟ +ﶠ +ﶡ +ﶢ +ﶣ +ﶤ +ﶥ +ﶦ +ﶧ +ﶨ +ﶩ +ﶪ +ﶫ +ﶬ +ﶭ +ﶮ +ﶯ +ﶰ +ﶱ +ﶲ +ﶳ +ﶴ +ﶵ +ﶶ +ﶷ +ﶸ +ﶹ +ﶺ +ﶻ +ﶼ +ﶽ +ﶾ +ﶿ +ﷀ +ﷁ +ﷂ +ﷃ +ﷄ +ﷅ +ﷆ +ﷇ +ﷰ +ﷱ +ﷲ +ﷳ +ﷴ +ﷵ +ﷶ +ﷷ +ﷸ +ﷹ +ﷺ +ﷻ +ﹰ +ﹱ +ﹲ +ﹳ +ﹴ +ﹶ +ﹷ +ﹸ +ﹹ +ﹺ +ﹻ +ﹼ +ﹽ +ﹾ +ﹿ +ﺀ +ﺁ +ﺂ +ﺃ +ﺄ +ﺅ +ﺆ +ﺇ +ﺈ +ﺉ +ﺊ +ﺋ +ﺌ +ﺍ +ﺎ +ﺏ +ﺐ +ﺑ +ﺒ +ﺓ +ﺔ +ﺕ +ﺖ +ﺗ +ﺘ +ﺙ +ﺚ +ﺛ +ﺜ +ﺝ +ﺞ +ﺟ +ﺠ +ﺡ +ﺢ +ﺣ +ﺤ +ﺥ +ﺦ +ﺧ +ﺨ +ﺩ +ﺪ +ﺫ +ﺬ +ﺭ +ﺮ +ﺯ +ﺰ +ﺱ +ﺲ +ﺳ +ﺴ +ﺵ +ﺶ +ﺷ +ﺸ +ﺹ +ﺺ +ﺻ +ﺼ +ﺽ +ﺾ +ﺿ +ﻀ +ﻁ +ﻂ +ﻃ +ﻄ +ﻅ +ﻆ +ﻇ +ﻈ +ﻉ +ﻊ +ﻋ +ﻌ +ﻍ +ﻎ +ﻏ +ﻐ +ﻑ +ﻒ +ﻓ +ﻔ +ﻕ +ﻖ +ﻗ +ﻘ +ﻙ +ﻚ +ﻛ +ﻜ +ﻝ +ﻞ +ﻟ +ﻠ +ﻡ +ﻢ +ﻣ +ﻤ +ﻥ +ﻦ +ﻧ +ﻨ +ﻩ +ﻪ +ﻫ +ﻬ +ﻭ +ﻮ +ﻯ +ﻰ +ﻱ +ﻲ +ﻳ +ﻴ +ﻵ +ﻶ +ﻷ +ﻸ +ﻹ +ﻺ +ﻻ +ﻼ +</classes> diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java index f7acf3eb5..8c213d7d5 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractBaseLayoutManager.java @@ -19,8 +19,12 @@ package org.apache.fop.layoutmgr; +import java.util.List; +import java.util.Stack; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.FObj; @@ -253,4 +257,20 @@ public abstract class AbstractBaseLayoutManager return fobj; } + /** {@inheritDoc} */ + public void reset() { + throw new UnsupportedOperationException("Not implemented"); + } + + /** {@inheritDoc} */ + public boolean isRestartable() { + return false; + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, + Position positionAtIPDChange, LayoutManager restartAtLM) { + throw new UnsupportedOperationException("Not implemented"); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java index 624bc907a..1a6f7cfb9 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractBreaker.java @@ -19,6 +19,8 @@ package org.apache.fop.layoutmgr; +import java.util.Collections; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -27,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.fo.Constants; +import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode; import org.apache.fop.traits.MinOptMax; import org.apache.fop.util.ListUtil; @@ -46,10 +49,10 @@ public abstract class AbstractBreaker { int footnoteLastListIndex; int footnoteLastElementIndex; - PageBreakPosition(LayoutManager lm, int iBreakIndex, + PageBreakPosition(LayoutManager lm, int breakIndex, int ffli, int ffei, int flli, int flei, double bpdA, int diff) { - super(lm, iBreakIndex); + super(lm, breakIndex); bpdAdjust = bpdA; difference = diff; footnoteFirstListIndex = ffli; @@ -59,6 +62,30 @@ public abstract class AbstractBreaker { } } + /** + * Helper method, mainly used to improve debug/trace output + * @param breakClassId the {@link Constants} enum value. + * @return the break class name + */ + static String getBreakClassName(int breakClassId) { + switch (breakClassId) { + case Constants.EN_ALL: return "ALL"; + case Constants.EN_ANY: return "ANY"; + case Constants.EN_AUTO: return "AUTO"; + case Constants.EN_COLUMN: return "COLUMN"; + case Constants.EN_EVEN_PAGE: return "EVEN PAGE"; + case Constants.EN_LINE: return "LINE"; + case Constants.EN_NONE: return "NONE"; + case Constants.EN_ODD_PAGE: return "ODD PAGE"; + case Constants.EN_PAGE: return "PAGE"; + default: return "??? (" + String.valueOf(breakClassId) + ")"; + } + } + + /** + * Helper class, extending the functionality of the + * basic {@link BlockKnuthSequence}. + */ public class BlockSequence extends BlockKnuthSequence { /** Number of elements to ignore at the beginning of the list. */ @@ -79,19 +106,21 @@ public abstract class AbstractBreaker { /** * Creates a new BlockSequence. - * @param iStartOn the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN, - * EN_ODD_PAGE, EN_EVEN_PAGE. + * @param startOn the kind of page the sequence should start on. + * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN}, + * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}. * @param displayAlign the value for the display-align property */ - public BlockSequence(int iStartOn, int displayAlign) { + public BlockSequence(int startOn, int displayAlign) { super(); - startOn = iStartOn; + this.startOn = startOn; this.displayAlign = displayAlign; } /** - * @return the kind of page the sequence should start on. One of EN_ANY, EN_COLUMN, - * EN_ODD_PAGE, EN_EVEN_PAGE. + * @return the kind of page the sequence should start on. + * One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN}, + * {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}. */ public int getStartOn() { return this.startOn; @@ -101,6 +130,7 @@ public abstract class AbstractBreaker { public int getDisplayAlign() { return this.displayAlign; } + /** * Finalizes a Knuth sequence. * @return a finalized sequence. @@ -142,6 +172,12 @@ public abstract class AbstractBreaker { } } + /** + * Finalizes a this {@link BlockSequence}, adding a terminating + * penalty-glue-penalty sequence + * @param breakPosition a Position instance pointing to the last penalty + * @return the finalized {@link BlockSequence} + */ public BlockSequence endBlockSequence(Position breakPosition) { KnuthSequence temp = endSequence(breakPosition); if (temp != null) { @@ -214,6 +250,11 @@ public abstract class AbstractBreaker { */ protected abstract List getNextKnuthElements(LayoutContext context, int alignment); + protected List getNextKnuthElements(LayoutContext context, int alignment, + Position positionAtIPDChange, LayoutManager restartAtLM) { + throw new UnsupportedOperationException("TODO: implement acceptable fallback"); + } + /** @return true if there's no content that could be handled. */ public boolean isEmpty() { return (this.blockLists.isEmpty()); @@ -260,14 +301,6 @@ public abstract class AbstractBreaker { /** * Starts the page breaking process. * @param flowBPD the constant available block-progression-dimension (used for every part) - */ - public void doLayout(int flowBPD) { - doLayout(flowBPD, false); - } - - /** - * Starts the page breaking process. - * @param flowBPD the constant available block-progression-dimension (used for every part) * @param autoHeight true if warnings about overflows should be disabled because the * the BPD is really undefined (for footnote-separators, for example) */ @@ -310,10 +343,7 @@ public abstract class AbstractBreaker { //debug code start if (log.isDebugEnabled()) { log.debug(" blockListIndex = " + blockListIndex); - String pagina = (blockList.startOn == Constants.EN_ANY) ? "any page" - : (blockList.startOn == Constants.EN_ODD_PAGE) ? "odd page" - : "even page"; - log.debug(" sequence starts on " + pagina); + log.debug(" sequence starts on " + getBreakClassName(blockList.startOn)); } observeElementList(blockList); //debug code end @@ -324,7 +354,6 @@ public abstract class AbstractBreaker { getPageProvider(), createLayoutListener(), alignment, alignmentLast, footnoteSeparatorLength, isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored()); - int iOptPageCount; BlockSequence effectiveList; if (getCurrentDisplayAlign() == Constants.EN_X_FILL) { @@ -335,22 +364,108 @@ public abstract class AbstractBreaker { effectiveList = blockList; } - //iOptPageCount = alg.firstFit(effectiveList, flowBPD, 1, true); alg.setConstantLineWidth(flowBPD); - iOptPageCount = alg.findBreakingPoints(effectiveList, /*flowBPD,*/ - 1, true, BreakingAlgorithm.ALL_BREAKS); - log.debug("PLM> iOptPageCount= " + iOptPageCount - + " pageBreaks.size()= " + alg.getPageBreaks().size()); + int optimalPageCount = alg.findBreakingPoints(effectiveList, 1, true, + BreakingAlgorithm.ALL_BREAKS); + if (alg.ipdChanged()) { + KnuthNode optimalBreak = alg.getBestNodeBeforeIPDChange(); + int positionIndex = optimalBreak.position; + KnuthElement elementAtBreak = alg.getElement(positionIndex); + Position positionAtBreak = elementAtBreak.getPosition(); + if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) { + throw new UnsupportedOperationException( + "Don't know how to restart at position" + positionAtBreak); + } + /* Retrieve the original position wrapped into this space position */ + positionAtBreak = positionAtBreak.getPosition(); + LayoutManager restartAtLM = null; + List firstElements = Collections.EMPTY_LIST; + if (containsNonRestartableLM(positionAtBreak)) { + firstElements = new LinkedList(); + boolean boxFound = false; + Iterator iter = effectiveList.listIterator(++positionIndex); + Position position = null; + while (iter.hasNext() + && (position == null || containsNonRestartableLM(position))) { + KnuthElement element = (KnuthElement) iter.next(); + positionIndex++; + position = element.getPosition(); + if (element.isBox()) { + boxFound = true; + firstElements.add(element); + } else if (boxFound) { + firstElements.add(element); + } + } + if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) { + /* Retrieve the original position wrapped into this space position */ + positionAtBreak = position.getPosition(); + } + } + if (positionAtBreak.getIndex() == -1) { + /* + * This is an indication that we are between two blocks + * (possibly surrounded by another block), not inside a + * paragraph. + */ + Position position; + Iterator iter = effectiveList.listIterator(positionIndex + 1); + do { + KnuthElement nextElement = (KnuthElement) iter.next(); + position = nextElement.getPosition(); + } while (position == null + || position instanceof SpaceResolver.SpaceHandlingPosition + || position instanceof SpaceResolver.SpaceHandlingBreakPosition + && position.getPosition().getIndex() == -1); + LayoutManager surroundingLM = positionAtBreak.getLM(); + while (position.getLM() != surroundingLM) { + position = position.getPosition(); + } + restartAtLM = position.getPosition().getLM(); + } + log.trace("IPD changes after page " + optimalPageCount + " at index " + + optimalBreak.position); + doPhase3(alg, optimalPageCount, blockList, effectiveList); + + blockLists.clear(); + blockListIndex = -1; + nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, + positionAtBreak, restartAtLM, firstElements); + } else { + log.debug("PLM> iOptPageCount= " + optimalPageCount + + " pageBreaks.size()= " + alg.getPageBreaks().size()); - //*** Phase 3: Add areas *** - doPhase3(alg, iOptPageCount, blockList, effectiveList); + //*** Phase 3: Add areas *** + doPhase3(alg, optimalPageCount, blockList, effectiveList); + } } } } /** + * Returns {@code true} if the given position or one of its descendants + * corresponds to a non-restartable LM. + * + * @param position a position + * @return {@code true} if there is a non-restartable LM in the hierarchy + */ + private boolean containsNonRestartableLM(Position position) { + LayoutManager lm = position.getLM(); + if (lm != null && !lm.isRestartable()) { + return true; + } else { + Position subPosition = position.getPosition(); + if (subPosition == null) { + return false; + } else { + return containsNonRestartableLM(subPosition); + } + } + } + + /** * Phase 3 of Knuth algorithm: Adds the areas * @param alg PageBreakingAlgorithm instance which determined the breaks * @param partCount number of parts (pages) to be rendered @@ -417,7 +532,7 @@ public abstract class AbstractBreaker { log.debug("PLM> part: " + (p + 1) + ", start at pos " + startElementIndex + ", break at pos " + endElementIndex - + ", break class = " + lastBreakClass); + + ", break class = " + getBreakClassName(lastBreakClass)); startPart(effectiveList, lastBreakClass); @@ -444,17 +559,9 @@ public abstract class AbstractBreaker { // at the beginning of the line effectiveListIterator = effectiveList .listIterator(startElementIndex); - KnuthElement firstElement; while (effectiveListIterator.hasNext() - && !(firstElement = (KnuthElement) effectiveListIterator.next()) + && !((KnuthElement) effectiveListIterator.next()) .isBox()) { - /* - if (firstElement.isGlue() && firstElement.getLayoutManager() != null) { - // discard the space representd by the glue element - ((BlockLevelLayoutManager) firstElement - .getLayoutManager()) - .discardSpace((KnuthGlue) firstElement); - }*/ startElementIndex++; } @@ -537,6 +644,7 @@ public abstract class AbstractBreaker { protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) { return nextSequenceStartsOn; } + /** * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty. * @param childLC LayoutContext to use @@ -545,12 +653,38 @@ public abstract class AbstractBreaker { */ protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) { + return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null); + } + + /** + * Gets the next block list (sequence) and adds it to a list of block lists + * if it's not empty. + * + * @param childLC LayoutContext to use + * @param nextSequenceStartsOn indicates on what page the next sequence + * should start + * @param positionAtIPDChange last element on the part before an IPD change + * @param restartAtLM the layout manager from which to restart, if IPD + * change occurs between two LMs + * @param firstElements elements from non-restartable LMs on the new page + * @return the page on which the next content should appear after a hard + * break + */ + protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, + Position positionAtIPDChange, LayoutManager restartAtLM, List firstElements) { updateLayoutContext(childLC); //Make sure the span change signal is reset childLC.signalSpanChange(Constants.NOT_SET); BlockSequence blockList; - List returnedList = getNextKnuthElements(childLC, alignment); + List returnedList; + if (positionAtIPDChange == null) { + returnedList = getNextKnuthElements(childLC, alignment); + } else { + returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange, + restartAtLM); + returnedList.addAll(0, firstElements); + } if (returnedList != null) { if (returnedList.isEmpty()) { nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn); @@ -562,26 +696,23 @@ public abstract class AbstractBreaker { nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn); Position breakPosition = null; - if (((KnuthElement) ListUtil.getLast(returnedList)).isForcedBreak()) { + if (ElementListUtils.endsWithForcedBreak(returnedList)) { KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil .removeLast(returnedList); breakPosition = breakPenalty.getPosition(); + log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass())); switch (breakPenalty.getBreakClass()) { case Constants.EN_PAGE: - log.debug("PLM> break - PAGE"); nextSequenceStartsOn = Constants.EN_ANY; break; case Constants.EN_COLUMN: - log.debug("PLM> break - COLUMN"); //TODO Fix this when implementing multi-column layout nextSequenceStartsOn = Constants.EN_COLUMN; break; case Constants.EN_ODD_PAGE: - log.debug("PLM> break - ODD PAGE"); nextSequenceStartsOn = Constants.EN_ODD_PAGE; break; case Constants.EN_EVEN_PAGE: - log.debug("PLM> break - EVEN PAGE"); nextSequenceStartsOn = Constants.EN_EVEN_PAGE; break; default: @@ -590,7 +721,7 @@ public abstract class AbstractBreaker { } } blockList.addAll(returnedList); - BlockSequence seq = null; + BlockSequence seq; seq = blockList.endBlockSequence(breakPosition); if (seq != null) { this.blockLists.add(seq); @@ -602,8 +733,8 @@ public abstract class AbstractBreaker { /** * Returns the average width of all the lines in the given range. * @param effectiveList effective block list to work on - * @param startElementIndex - * @param endElementIndex + * @param startElementIndex index of the element starting the range + * @param endElementIndex index of the element ending the range * @return the average line length, 0 if there's no content */ private int optimizeLineLength(KnuthSequence effectiveList, int startElementIndex, int endElementIndex) { @@ -854,29 +985,6 @@ public abstract class AbstractBreaker { log.debug("AdjustLineNumbers: difference " + difference + " / " + total + " on " + lineList.size() + " elements"); } -// int adjustedDiff = 0; -// int partial = 0; -// KnuthGlue prevLine = null; -// KnuthGlue currLine = null; -// ListIterator lineListIterator = lineList.listIterator(); -// while (lineListIterator.hasNext()) { -// currLine = (KnuthGlue)lineListIterator.next(); -// if (prevLine != null -// && prevLine.getLayoutManager() != currLine.getLayoutManager()) { -// int newAdjust = ((BlockLevelLayoutManager) prevLine.getLayoutManager()) -// .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, prevLine); -// adjustedDiff += newAdjust; -// } -// partial += (difference > 0 ? currLine.getY() : currLine.getZ()); -// prevLine = currLine; -// } -// if (currLine != null) { -// int newAdjust = ((BlockLevelLayoutManager) currLine.getLayoutManager()) -// .negotiateBPDAdjustment(((int) ((float) partial * difference / total)) - adjustedDiff, currLine); -// adjustedDiff += newAdjust; -// } -// return adjustedDiff; - ListIterator lineListIterator = lineList.listIterator(); int adjustedDiff = 0; int partial = 0; diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java index 8dca1c749..82f0599eb 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java @@ -47,22 +47,22 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager private static Log log = LogFactory.getLog(AbstractLayoutManager.class); /** Parent LayoutManager for this LayoutManager */ - protected LayoutManager parentLM = null; + protected LayoutManager parentLM; /** List of child LayoutManagers */ - protected List childLMs = null; + protected List childLMs; /** Iterator for child LayoutManagers */ - protected ListIterator fobjIter = null; + protected ListIterator fobjIter; /** Marker map for markers related to this LayoutManager */ - private Map markers = null; + private Map markers; /** True if this LayoutManager has handled all of its content. */ - private boolean isFinished = false; + private boolean isFinished; /** child LM during getNextKnuthElement phase */ - protected LayoutManager curChildLM = null; + protected LayoutManager curChildLM; /** child LM iterator during getNextKnuthElement phase */ - protected ListIterator childLMiter = null; + protected ListIterator childLMiter; private int lastGeneratedPosition = -1; private int smallestPosNumberChecked = Integer.MAX_VALUE; @@ -122,6 +122,14 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager return null; } + protected void setCurrentChildLM(LayoutManager childLM) { + curChildLM = childLM; + childLMiter = new LMiter(this); + do { + curChildLM = (LayoutManager) childLMiter.next(); + } while (curChildLM != childLM); + } + /** * Return indication if getChildLM will return another LM. * @return true if another child LM is still available @@ -450,4 +458,22 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager return (super.toString() + (fobj != null ? "[fobj=" + fobj.toString() + "]" : "")); } + /** {@inheritDoc} */ + public void reset() { + isFinished = false; + curChildLM = null; + childLMiter = new LMiter(this); + /* + * Reset the children LM. Can't rely on childLMiter since it may have + * been set to null in checkEndOfLayout. + */ + for (LMiter iter = new LMiter(this); iter.hasNext();) { + ((LayoutManager) iter.next()).reset(); + } + if (fobj != null) { + markers = fobj.getMarkers(); + } + lastGeneratedPosition = -1; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/AbstractPageSequenceLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractPageSequenceLayoutManager.java index 758761303..0fa046aee 100644 --- a/src/java/org/apache/fop/layoutmgr/AbstractPageSequenceLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/AbstractPageSequenceLayoutManager.java @@ -382,4 +382,9 @@ public abstract class AbstractPageSequenceLayoutManager extends AbstractLayoutMa } } + /** {@inheritDoc} */ + public void reset() { + throw new IllegalStateException(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java index a429359ad..14183c52e 100644 --- a/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java +++ b/src/java/org/apache/fop/layoutmgr/AreaAdditionUtil.java @@ -117,7 +117,7 @@ public class AreaAdditionUtil { // set space after for each LM, in order to implement // display-align = distribute lc.setSpaceAfter(layoutContext.getSpaceAfter()); - lc.setStackLimitsFrom(layoutContext); + lc.setStackLimitBP(layoutContext.getStackLimitBP()); childLM.addAreas(childPosIter, lc); } diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java index b6dd4d082..e86c5feaf 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java @@ -24,6 +24,7 @@ import java.awt.geom.Rectangle2D; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -37,6 +38,7 @@ import org.apache.fop.datatypes.FODimension; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.flow.BlockContainer; import org.apache.fop.fo.properties.CommonAbsolutePosition; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.traits.MinOptMax; import org.apache.fop.traits.SpaceVal; import org.apache.fop.util.ListUtil; @@ -261,7 +263,215 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager if (!firstVisibleMarkServed) { addKnuthElementsForSpaceBefore(returnList, alignment); - context.updateKeepWithPreviousPending(getKeepWithPreviousStrength()); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); + } + + addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); + firstVisibleMarkServed = true; + + if (autoHeight && inlineElementList) { + //Spaces, border and padding to be repeated at each break + addPendingMarks(context); + + LayoutManager curLM; // currently active LM + LayoutManager prevLM = null; // previously active LM + while ((curLM = getChildLM()) != null) { + LayoutContext childLC = new LayoutContext(0); + childLC.copyPendingMarksFrom(context); + // curLM is a ? + childLC.setStackLimitBP(MinOptMax.subtract(context.getStackLimitBP(), stackLimit)); + childLC.setRefIPD(relDims.ipd); + childLC.setWritingMode(getBlockContainerFO().getWritingMode()); + if (curLM == this.childLMs.get(0)) { + childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); + //Handled already by the parent (break collapsing, see above) + } + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { + //Propagate keep-with-previous up from the first child + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + childLC.clearKeepWithPreviousPending(); + } + if (returnedList.size() == 1 + && ((ListElement)returnedList.get(0)).isForcedBreak()) { + // a descendant of this block has break-before + /* + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + bSpaceBeforeServed = false; + }*/ + contentList.addAll(returnedList); + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + addInBetweenBreak(contentList, context, childLC); + } + contentList.addAll(returnedList); + if (returnedList.isEmpty()) { + //Avoid NoSuchElementException below (happens with empty blocks) + continue; + } + if (ElementListUtils.endsWithForcedBreak(returnedList)) { + // a descendant of this block has break-after + if (curLM.isFinished()) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } + } + // propagate and clear + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + childLC.clearKeepsPending(); + prevLM = curLM; + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + } else { + MinOptMax range = new MinOptMax(relDims.ipd); + BlockContainerBreaker breaker = new BlockContainerBreaker(this, range); + breaker.doLayout(relDims.bpd, autoHeight); + boolean contentOverflows = breaker.isOverflow(); + if (autoHeight) { + //Update content BPD now that it is known + int newHeight = breaker.deferredAlg.totalWidth; + boolean switchedProgressionDirection + = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); + if (switchedProgressionDirection) { + setContentAreaIPD(newHeight); + } else { + vpContentBPD = newHeight; + } + updateRelDims(contentRectOffsetX, contentRectOffsetY, false); + } + + Position bcPosition = new BlockContainerPosition(this, breaker); + returnList.add(new KnuthBox(vpContentBPD, notifyPos(bcPosition), false)); + //TODO Handle min/opt/max for block-progression-dimension + /* These two elements will be used to add stretchability to the above box + returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE, + false, returnPosition, false)); + returnList.add(new KnuthGlue(0, 1 * constantLineHeight, 0, + LINE_NUMBER_ADJUSTMENT, returnPosition, false)); + */ + + if (contentOverflows) { + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getBlockContainerFO().getUserAgent().getEventBroadcaster()); + boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW); + eventProducer.viewportOverflow(this, getBlockContainerFO().getName(), + breaker.getOverflowAmount(), needClip(), canRecover, + getBlockContainerFO().getLocator()); + } + } + addKnuthElementsForBorderPaddingAfter(returnList, true); + addKnuthElementsForSpaceAfter(returnList, alignment); + + //All child content is processed. Only break-after can occur now, so... + context.clearPendingMarks(); + addKnuthElementsForBreakAfter(returnList, context); + + context.updateKeepWithNextPending(getKeepWithNext()); + + setFinished(true); + return returnList; + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, + Position restartPosition, LayoutManager restartAtLM) { + resetSpaces(); + if (isAbsoluteOrFixed()) { + return getNextKnuthElementsAbsolute(context, alignment); + } + + autoHeight = false; + //boolean rotated = (getBlockContainerFO().getReferenceOrientation() % 180 != 0); + int maxbpd = context.getStackLimitBP().opt; + int allocBPD; + if (height.getEnum() == EN_AUTO + || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) { + //auto height when height="auto" or "if that dimension is not specified explicitly + //(i.e., it depends on content's block-progression-dimension)" (XSL 1.0, 7.14.1) + allocBPD = maxbpd; + autoHeight = true; + if (getBlockContainerFO().getReferenceOrientation() == 0) { + //Cannot easily inline element list when ref-or="180" + inlineElementList = true; + } + } else { + allocBPD = height.getValue(this); //this is the content-height + allocBPD += getBPIndents(); + } + vpContentBPD = allocBPD - getBPIndents(); + + referenceIPD = context.getRefIPD(); + if (width.getEnum() == EN_AUTO) { + updateContentAreaIPDwithOverconstrainedAdjust(); + } else { + int contentWidth = width.getValue(this); + updateContentAreaIPDwithOverconstrainedAdjust(contentWidth); + } + + double contentRectOffsetX = 0; + contentRectOffsetX += getBlockContainerFO() + .getCommonMarginBlock().startIndent.getValue(this); + double contentRectOffsetY = 0; + contentRectOffsetY += getBlockContainerFO() + .getCommonBorderPaddingBackground().getBorderBeforeWidth(false); + contentRectOffsetY += getBlockContainerFO() + .getCommonBorderPaddingBackground().getPaddingBefore(false, this); + + updateRelDims(contentRectOffsetX, contentRectOffsetY, autoHeight); + + int availableIPD = referenceIPD - getIPIndents(); + if (getContentAreaIPD() > availableIPD) { + BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get( + getBlockContainerFO().getUserAgent().getEventBroadcaster()); + eventProducer.objectTooWide(this, getBlockContainerFO().getName(), + getContentAreaIPD(), context.getRefIPD(), + getBlockContainerFO().getLocator()); + } + + MinOptMax stackLimit = new MinOptMax(relDims.bpd); + + List returnedList; + List contentList = new LinkedList(); + List returnList = new LinkedList(); + + if (!breakBeforeServed) { + breakBeforeServed = true; + if (!context.suppressBreakBefore()) { + if (addKnuthElementsForBreakBefore(returnList, context)) { + return returnList; + } + } + } + + if (!firstVisibleMarkServed) { + addKnuthElementsForSpaceBefore(returnList, alignment); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); } addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); @@ -273,8 +483,99 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager BlockLevelLayoutManager curLM; // currently active LM BlockLevelLayoutManager prevLM = null; // previously active LM + + LayoutContext childLC = new LayoutContext(0); + if (lmStack.isEmpty()) { + assert restartAtLM != null && restartAtLM.getParent() == this; + curLM = (BlockLevelLayoutManager) restartAtLM; + curLM.reset(); + setCurrentChildLM(curLM); + + childLC.copyPendingMarksFrom(context); + childLC.setStackLimitBP(MinOptMax.subtract(context.getStackLimitBP(), stackLimit)); + childLC.setRefIPD(relDims.ipd); + childLC.setWritingMode(getBlockContainerFO().getWritingMode()); + if (curLM == this.childLMs.get(0)) { + childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); + //Handled already by the parent (break collapsing, see above) + } + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment); + } else { + curLM = (BlockLevelLayoutManager) lmStack.pop(); + setCurrentChildLM(curLM); + + childLC.copyPendingMarksFrom(context); + childLC.setStackLimitBP(MinOptMax.subtract(context.getStackLimitBP(), stackLimit)); + childLC.setRefIPD(relDims.ipd); + childLC.setWritingMode(getBlockContainerFO().getWritingMode()); + if (curLM == this.childLMs.get(0)) { + childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); + //Handled already by the parent (break collapsing, see above) + } + + // get elements from curLM + returnedList = curLM.getNextKnuthElements(childLC, alignment, lmStack, + restartPosition, restartAtLM); + } + if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { + //Propagate keep-with-previous up from the first child + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + childLC.clearKeepWithPreviousPending(); + } + if (returnedList.size() == 1 + && ((ListElement)returnedList.get(0)).isForcedBreak()) { + // a descendant of this block has break-before + /* + if (returnList.size() == 0) { + // the first child (or its first child ...) has + // break-before; + // all this block, including space before, will be put in + // the + // following page + bSpaceBeforeServed = false; + }*/ + contentList.addAll(returnedList); + + // "wrap" the Position inside each element + // moving the elements from contentList to returnList + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } else { + if (prevLM != null) { + // there is a block handled by prevLM + // before the one handled by curLM + addInBetweenBreak(contentList, context, childLC); + } + contentList.addAll(returnedList); + if (!returnedList.isEmpty()) { + if (((ListElement) ListUtil.getLast(returnedList)) + .isForcedBreak()) { + // a descendant of this block has break-after + if (curLM.isFinished()) { + // there is no other content in this block; + // it's useless to add space after before a page break + setFinished(true); + } + + returnedList = new LinkedList(); + wrapPositionElements(contentList, returnList); + + return returnList; + } + } + } + // propagate and clear + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + childLC.clearKeepsPending(); + prevLM = curLM; + while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { - LayoutContext childLC = new LayoutContext(0); + curLM.reset(); + childLC = new LayoutContext(0); childLC.copyPendingMarksFrom(context); // curLM is a ? childLC.setStackLimitBP(MinOptMax.subtract(context.getStackLimitBP(), stackLimit)); @@ -391,12 +692,17 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager context.clearPendingMarks(); addKnuthElementsForBreakAfter(returnList, context); - context.updateKeepWithNextPending(getKeepWithNextStrength()); + context.updateKeepWithNextPending(getKeepWithNext()); setFinished(true); return returnList; } + /** {@inheritDoc} */ + public boolean isRestartable() { + return true; + } + private List getNextKnuthElementsAbsolute(LayoutContext context, int alignment) { autoHeight = false; @@ -1011,23 +1317,18 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KeepUtil.getCombinedBlockLevelKeepStrength( - getBlockContainerFO().getKeepTogether()); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getBlockContainerFO().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength( - getBlockContainerFO().getKeepWithNext()); + public KeepProperty getKeepWithPreviousProperty() { + return getBlockContainerFO().getKeepWithPrevious(); } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength( - getBlockContainerFO().getKeepWithPrevious()); + public KeepProperty getKeepWithNextProperty() { + return getBlockContainerFO().getKeepWithNext(); } /** diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index acfcbe3f0..53dc5b38c 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -22,6 +22,7 @@ package org.apache.fop.layoutmgr; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,9 +68,6 @@ public class BlockLayoutManager extends BlockStackingLayoutManager private MinOptMax effSpaceBefore; private MinOptMax effSpaceAfter; - /** The list of child BreakPoss instances. */ - protected List childBreaks = new java.util.ArrayList(); - /** * Creates a new BlockLayoutManager. * @param inBlock the block FO object to create the layout manager for. @@ -114,8 +112,19 @@ public class BlockLayoutManager extends BlockStackingLayoutManager /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { + return getNextKnuthElements(context, alignment, null, null, null); + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, + Position restartPosition, LayoutManager restartAtLM) { resetSpaces(); - return super.getNextKnuthElements(context, alignment); + if (lmStack == null) { + return super.getNextKnuthElements(context, alignment); + } else { + return super.getNextKnuthElements(context, alignment, lmStack, restartPosition, + restartAtLM); + } } private void resetSpaces() { @@ -210,21 +219,18 @@ public class BlockLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - KeepProperty keep = getBlockFO().getKeepTogether(); - int strength = KeepUtil.getCombinedBlockLevelKeepStrength(keep); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getBlockFO().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getBlockFO().getKeepWithNext()); + public KeepProperty getKeepWithPreviousProperty() { + return getBlockFO().getKeepWithPrevious(); } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getBlockFO().getKeepWithPrevious()); + public KeepProperty getKeepWithNextProperty() { + return getBlockFO().getKeepWithNext(); } /** {@inheritDoc} */ @@ -252,8 +258,8 @@ public class BlockLayoutManager extends BlockStackingLayoutManager // and put them in a new list; LinkedList positionList = new LinkedList(); Position pos; - boolean bSpaceBefore = false; - boolean bSpaceAfter = false; + boolean spaceBefore = false; + boolean spaceAfter = false; Position firstPos = null; Position lastPos = null; while (parentIter.hasNext()) { @@ -276,11 +282,11 @@ public class BlockLayoutManager extends BlockStackingLayoutManager // this means the space was not discarded if (positionList.size() == 0) { // pos was in the element representing space-before - bSpaceBefore = true; + spaceBefore = true; //log.trace(" space before"); } else { // pos was in the element representing space-after - bSpaceAfter = true; + spaceAfter = true; //log.trace(" space-after"); } } else if (innerPosition.getLM() == this @@ -305,7 +311,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager // the Positions in positionList were inside the elements // created by the LineLM childPosIter = new StackingIter(positionList.listIterator()); - } else { + } else { // the Positions in positionList were inside the elements // created by the BlockLM in the createUnitElements() method //if (((Position) positionList.getLast()) instanceof @@ -344,7 +350,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager // + " spacing"); // add space before and / or after the paragraph // to reach a multiple of bpUnit - if (bSpaceBefore && bSpaceAfter) { + if (spaceBefore && spaceAfter) { foSpaceBefore = new SpaceVal(getBlockFO() .getCommonMarginBlock().spaceBefore, this).getSpace(); foSpaceAfter = new SpaceVal(getBlockFO() @@ -357,7 +363,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager + foSpaceBefore.min + foSpaceAfter.min) * bpUnit - splitLength - adjustedSpaceBefore; - } else if (bSpaceBefore) { + } else if (spaceBefore) { adjustedSpaceBefore = neededUnits(splitLength + foSpaceBefore.min) * bpUnit - splitLength; @@ -551,5 +557,10 @@ public class BlockLayoutManager extends BlockStackingLayoutManager } } + /** {@inheritDoc} */ + public boolean isRestartable() { + return true; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java index 9163193a2..3d30abde0 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLevelLayoutManager.java @@ -19,6 +19,8 @@ package org.apache.fop.layoutmgr; +import org.apache.fop.fo.properties.KeepProperty; + /** * The interface for LayoutManagers which generate block areas */ @@ -35,11 +37,6 @@ public interface BlockLevelLayoutManager extends LayoutManager { /** Adjustment class: adjustment for line height */ int LINE_HEIGHT_ADJUSTMENT = 3; - /** The integer value for "auto" keep strength */ - int KEEP_AUTO = Integer.MIN_VALUE; - /** The integer value for "always" keep strength */ - int KEEP_ALWAYS = Integer.MAX_VALUE; - int negotiateBPDAdjustment(int adj, KnuthElement lastElement); void discardSpace(KnuthGlue spaceGlue); @@ -48,7 +45,7 @@ public interface BlockLevelLayoutManager extends LayoutManager { * Returns the keep-together strength for this element. * @return the keep-together strength */ - int getKeepTogetherStrength(); + Keep getKeepTogether(); /** * @return true if this element must be kept together @@ -59,7 +56,7 @@ public interface BlockLevelLayoutManager extends LayoutManager { * Returns the keep-with-previous strength for this element. * @return the keep-with-previous strength */ - int getKeepWithPreviousStrength(); + Keep getKeepWithPrevious(); /** * @return true if this element must be kept with the previous element. @@ -70,11 +67,28 @@ public interface BlockLevelLayoutManager extends LayoutManager { * Returns the keep-with-next strength for this element. * @return the keep-with-next strength */ - int getKeepWithNextStrength(); + Keep getKeepWithNext(); /** * @return true if this element must be kept with the next element. */ boolean mustKeepWithNext(); + /** + * Returns the keep-together property specified on the FObj. + * @return the keep-together property + */ + KeepProperty getKeepTogetherProperty(); + + /** + * Returns the keep-with-previous property specified on the FObj. + * @return the keep-together property + */ + KeepProperty getKeepWithPreviousProperty(); + + /** + * Returns the keep-with-next property specified on the FObj. + * @return the keep-together property + */ + KeepProperty getKeepWithNextProperty(); } diff --git a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java index 5a44c8391..53c529eaa 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -30,9 +31,11 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.BlockParent; +import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; import org.apache.fop.fo.properties.BreakPropertySet; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.SpaceProperty; import org.apache.fop.layoutmgr.inline.InlineLayoutManager; import org.apache.fop.layoutmgr.inline.LineLayoutManager; @@ -52,31 +55,26 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager */ private static Log log = LogFactory.getLog(BlockStackingLayoutManager.class); - /** - * Reference to FO whose areas it's managing or to the traits - * of the FO. - */ - //protected LayoutManager curChildLM = null; AbstractLayoutManager also defines this! - protected BlockParent parentArea = null; + protected BlockParent parentArea; /** Value of the block-progression-unit (non-standard property) */ - protected int bpUnit = 0; + protected int bpUnit; /** space-before value adjusted for block-progression-unit handling */ - protected int adjustedSpaceBefore = 0; + protected int adjustedSpaceBefore; /** space-after value adjusted for block-progression-unit handling */ - protected int adjustedSpaceAfter = 0; + protected int adjustedSpaceAfter; /** Only used to store the original list when createUnitElements is called */ - protected List storedList = null; + protected List storedList; /** Indicates whether break before has been served or not */ - protected boolean breakBeforeServed = false; + protected boolean breakBeforeServed; /** Indicates whether the first visible mark has been returned by this LM, yet */ - protected boolean firstVisibleMarkServed = false; + protected boolean firstVisibleMarkServed; /** Reference IPD available */ - protected int referenceIPD = 0; + protected int referenceIPD; /** the effective start-indent value */ - protected int startIndent = 0; + protected int startIndent; /** the effective end-indent value */ - protected int endIndent = 0; + protected int endIndent; /** * Holds the (one-time use) fo:block space-before * and -after properties. Large fo:blocks are split @@ -86,13 +84,13 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager * Block and space-after at the end of the last Block * used in rendering the fo:block. */ - protected MinOptMax foSpaceBefore = null; + protected MinOptMax foSpaceBefore; /** see foSpaceBefore */ - protected MinOptMax foSpaceAfter = null; + protected MinOptMax foSpaceAfter; private Position auxiliaryPosition; - private int contentAreaIPD = 0; + private int contentAreaIPD; /** * @param node the fo this LM deals with @@ -246,38 +244,27 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { - //log.debug("BLM.getNextKnuthElements> keep-together = " - // + layoutProps.keepTogether.getType()); - //log.debug(" keep-with-previous = " + - // layoutProps.keepWithPrevious.getType()); - //log.debug(" keep-with-next = " + - // layoutProps.keepWithNext.getType()); - BlockLevelLayoutManager curLM; // currently active LM - BlockLevelLayoutManager prevLM = null; // previously active LM - referenceIPD = context.getRefIPD(); - updateContentAreaIPDwithOverconstrainedAdjust(); - List returnedList = null; List contentList = new LinkedList(); - List returnList = new LinkedList(); + List elements = new LinkedList(); if (!breakBeforeServed) { breakBeforeServed = true; if (!context.suppressBreakBefore()) { - if (addKnuthElementsForBreakBefore(returnList, context)) { - return returnList; + if (addKnuthElementsForBreakBefore(elements, context)) { + return elements; } } } if (!firstVisibleMarkServed) { - addKnuthElementsForSpaceBefore(returnList, alignment); - context.updateKeepWithPreviousPending(getKeepWithPreviousStrength()); + addKnuthElementsForSpaceBefore(elements, alignment); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); } - addKnuthElementsForBorderPaddingBefore(returnList, !firstVisibleMarkServed); + addKnuthElementsForBorderPaddingBefore(elements, !firstVisibleMarkServed); firstVisibleMarkServed = true; //Spaces, border and padding to be repeated at each break @@ -286,171 +273,340 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager //Used to indicate a special break-after case when all content has already been generated. BreakElement forcedBreakAfterLast = null; - while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { + LayoutManager currentChildLM; + while ((currentChildLM = (LayoutManager) getChildLM()) != null) { LayoutContext childLC = new LayoutContext(0); - childLC.copyPendingMarksFrom(context); - if (curLM instanceof LineLayoutManager) { - // curLM is a LineLayoutManager - // set stackLimit for lines (stack limit is now i-p-direction, not b-p-direction!) - childLC.setStackLimitBP(context.getStackLimitBP()); - childLC.setStackLimitIP(new MinOptMax(getContentAreaIPD())); - childLC.setRefIPD(getContentAreaIPD()); - } else { - // curLM is a ? - //childLC.setStackLimit(MinOptMax.subtract(context - // .getStackLimit(), stackSize)); - childLC.setStackLimitBP(context.getStackLimitBP()); - childLC.setRefIPD(referenceIPD); - } - if (curLM == this.childLMs.get(0)) { - childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); - //Handled already by the parent (break collapsing, see above) - } - // get elements from curLM - returnedList = curLM.getNextKnuthElements(childLC, alignment); - if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { + List childrenElements = getNextChildElements(currentChildLM, context, childLC, + alignment); + + if (contentList.isEmpty()) { //Propagate keep-with-previous up from the first child context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); - childLC.clearKeepWithPreviousPending(); } - if (returnedList != null - && returnedList.size() == 1 - && ((ListElement) returnedList.get(0)).isForcedBreak()) { + if (childrenElements != null && !childrenElements.isEmpty()) { + if (!contentList.isEmpty() + && !ElementListUtils.startsWithForcedBreak(childrenElements)) { + // there is a block handled by prevLM before the one + // handled by curLM, and the one handled + // by the current LM does not begin with a break + addInBetweenBreak(contentList, context, childLC); + } + if (childrenElements.size() == 1 + && ElementListUtils.startsWithForcedBreak(childrenElements)) { - if (curLM.isFinished() && !hasNextChildLM()) { + if (currentChildLM.isFinished() && !hasNextChildLM()) { + // a descendant of this block has break-before + forcedBreakAfterLast = (BreakElement) childrenElements.get(0); + context.clearPendingMarks(); + break; + } + + if (contentList.isEmpty()) { + // Empty fo:block, zero-length box makes sure the IDs and/or markers + // are registered and borders/padding are painted. + elements.add(new KnuthBox(0, notifyPos(new Position(this)), false)); + } // a descendant of this block has break-before - forcedBreakAfterLast = (BreakElement) returnedList.get(0); + contentList.addAll(childrenElements); + + wrapPositionElements(contentList, elements); + + return elements; + } else { + contentList.addAll(childrenElements); + if (ElementListUtils.endsWithForcedBreak(childrenElements)) { + // a descendant of this block has break-after + if (currentChildLM.isFinished() && !hasNextChildLM()) { + forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList); + context.clearPendingMarks(); + break; + } + + wrapPositionElements(contentList, elements); + + return elements; + } + } + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + } + } + + if (!contentList.isEmpty()) { + wrapPositionElements(contentList, elements); + } else if (forcedBreakAfterLast == null) { + // Empty fo:block, zero-length box makes sure the IDs and/or markers + // are registered. + elements.add(new KnuthBox(0, notifyPos(new Position(this)), true)); + } + + addKnuthElementsForBorderPaddingAfter(elements, true); + addKnuthElementsForSpaceAfter(elements, alignment); + + //All child content is processed. Only break-after can occur now, so... + context.clearPendingMarks(); + if (forcedBreakAfterLast == null) { + addKnuthElementsForBreakAfter(elements, context); + } else { + forcedBreakAfterLast.clearPendingMarks(); + elements.add(forcedBreakAfterLast); + } + + context.updateKeepWithNextPending(getKeepWithNext()); + + setFinished(true); + + return elements; + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, + Position restartPosition, LayoutManager restartAtLM) { + referenceIPD = context.getRefIPD(); + updateContentAreaIPDwithOverconstrainedAdjust(); + + List contentList = new LinkedList(); + List elements = new LinkedList(); + + if (!breakBeforeServed) { + breakBeforeServed = true; + if (!context.suppressBreakBefore()) { + if (addKnuthElementsForBreakBefore(elements, context)) { + return elements; + } + } + } + + if (!firstVisibleMarkServed) { + addKnuthElementsForSpaceBefore(elements, alignment); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); + } + + addKnuthElementsForBorderPaddingBefore(elements, !firstVisibleMarkServed); + firstVisibleMarkServed = true; + + //Spaces, border and padding to be repeated at each break + addPendingMarks(context); + + //Used to indicate a special break-after case when all content has already been generated. + BreakElement forcedBreakAfterLast = null; + + LayoutContext childLC = new LayoutContext(0); + List childrenElements; + LayoutManager currentChildLM; + if (lmStack.isEmpty()) { + assert restartAtLM != null && restartAtLM.getParent() == this; + currentChildLM = restartAtLM; + currentChildLM.reset(); + setCurrentChildLM(currentChildLM); + + childrenElements = getNextChildElements(currentChildLM, context, childLC, + alignment); + } else { + currentChildLM = (BlockLevelLayoutManager) lmStack.pop(); + setCurrentChildLM(currentChildLM); + childrenElements = getNextChildElements(currentChildLM, context, childLC, alignment, + lmStack, restartPosition, restartAtLM); + } + + if (contentList.isEmpty()) { + //Propagate keep-with-previous up from the first child + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + } + if (childrenElements != null && !childrenElements.isEmpty()) { + if (!contentList.isEmpty() + && !ElementListUtils.startsWithForcedBreak(childrenElements)) { + // there is a block handled by prevLM before the one + // handled by curLM, and the one handled + // by the current LM does not begin with a break + addInBetweenBreak(contentList, context, childLC); + } + if (childrenElements.size() == 1 + && ElementListUtils.startsWithForcedBreak(childrenElements)) { + + if (currentChildLM.isFinished() && !hasNextChildLM()) { + // a descendant of this block has break-before + forcedBreakAfterLast = (BreakElement) childrenElements.get(0); context.clearPendingMarks(); - break; +// break; TODO } if (contentList.isEmpty()) { // Empty fo:block, zero-length box makes sure the IDs and/or markers // are registered and borders/padding are painted. - returnList.add(new KnuthBox(0, notifyPos(new Position(this)), false)); + elements.add(new KnuthBox(0, notifyPos(new Position(this)), false)); } // a descendant of this block has break-before - contentList.addAll(returnedList); - - /* extension: conversione di tutta la sequenza fin'ora ottenuta */ - if (bpUnit > 0) { - storedList = contentList; - contentList = createUnitElements(contentList); - } - /* end of extension */ + contentList.addAll(childrenElements); - // "wrap" the Position inside each element - // moving the elements from contentList to returnList - returnedList = new LinkedList(); - wrapPositionElements(contentList, returnList); + wrapPositionElements(contentList, elements); - return returnList; + return elements; } else { - if (returnedList == null || returnedList.isEmpty()) { - //Avoid NoSuchElementException below (happens with empty blocks) - continue; + contentList.addAll(childrenElements); + if (ElementListUtils.endsWithForcedBreak(childrenElements)) { + // a descendant of this block has break-after + if (currentChildLM.isFinished() && !hasNextChildLM()) { + forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList); + context.clearPendingMarks(); +// break; TODO + } + + wrapPositionElements(contentList, elements); + + return elements; } - if (prevLM != null - && !ElementListUtils.startsWithForcedBreak(returnedList)) { + } + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + } + + while ((currentChildLM = (LayoutManager) getChildLM()) != null) { + currentChildLM.reset(); // TODO won't work with forced breaks + + childLC = new LayoutContext(0); + + childrenElements = getNextChildElements(currentChildLM, context, childLC, + alignment); + + if (contentList.isEmpty()) { + //Propagate keep-with-previous up from the first child + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + } + if (childrenElements != null && !childrenElements.isEmpty()) { + if (!contentList.isEmpty() + && !ElementListUtils.startsWithForcedBreak(childrenElements)) { // there is a block handled by prevLM before the one // handled by curLM, and the one handled // by the current LM does not begin with a break addInBetweenBreak(contentList, context, childLC); } - contentList.addAll(returnedList); - if (ElementListUtils.endsWithForcedBreak(returnedList)) { - // a descendant of this block has break-after - if (curLM.isFinished() && !hasNextChildLM()) { - forcedBreakAfterLast = (BreakElement) ListUtil - .removeLast(contentList); + if (childrenElements.size() == 1 + && ElementListUtils.startsWithForcedBreak(childrenElements)) { + + if (currentChildLM.isFinished() && !hasNextChildLM()) { + // a descendant of this block has break-before + forcedBreakAfterLast = (BreakElement) childrenElements.get(0); context.clearPendingMarks(); break; } - /* extension: conversione di tutta la sequenza fin'ora ottenuta */ - if (bpUnit > 0) { - storedList = contentList; - contentList = createUnitElements(contentList); + if (contentList.isEmpty()) { + // Empty fo:block, zero-length box makes sure the IDs and/or markers + // are registered and borders/padding are painted. + elements.add(new KnuthBox(0, notifyPos(new Position(this)), false)); } - /* end of extension */ + // a descendant of this block has break-before + contentList.addAll(childrenElements); - returnedList = new LinkedList(); - wrapPositionElements(contentList, returnList); + wrapPositionElements(contentList, elements); - return returnList; + return elements; + } else { + contentList.addAll(childrenElements); + if (ElementListUtils.endsWithForcedBreak(childrenElements)) { + // a descendant of this block has break-after + if (currentChildLM.isFinished() && !hasNextChildLM()) { + forcedBreakAfterLast = (BreakElement) ListUtil.removeLast(contentList); + context.clearPendingMarks(); + break; + } + + wrapPositionElements(contentList, elements); + + return elements; + } } + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); } - // propagate and clear - context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); - childLC.clearKeepsPending(); - prevLM = curLM; - } - - /* Extension: conversione di tutta la sequenza fin'ora ottenuta */ - if (bpUnit > 0) { - storedList = contentList; - contentList = createUnitElements(contentList); } - /* end of extension */ - returnedList = new LinkedList(); if (!contentList.isEmpty()) { - wrapPositionElements(contentList, returnList); + wrapPositionElements(contentList, elements); } else if (forcedBreakAfterLast == null) { // Empty fo:block, zero-length box makes sure the IDs and/or markers // are registered. - returnList.add(new KnuthBox(0, notifyPos(new Position(this)), true)); + elements.add(new KnuthBox(0, notifyPos(new Position(this)), true)); } - addKnuthElementsForBorderPaddingAfter(returnList, true); - addKnuthElementsForSpaceAfter(returnList, alignment); + addKnuthElementsForBorderPaddingAfter(elements, true); + addKnuthElementsForSpaceAfter(elements, alignment); //All child content is processed. Only break-after can occur now, so... context.clearPendingMarks(); if (forcedBreakAfterLast == null) { - addKnuthElementsForBreakAfter(returnList, context); - } - - if (forcedBreakAfterLast != null) { + addKnuthElementsForBreakAfter(elements, context); + } else { forcedBreakAfterLast.clearPendingMarks(); - returnList.add(forcedBreakAfterLast); + elements.add(forcedBreakAfterLast); } - context.updateKeepWithNextPending(getKeepWithNextStrength()); + context.updateKeepWithNextPending(getKeepWithNext()); setFinished(true); - return returnList; + return elements; + } + + private List getNextChildElements(LayoutManager childLM, LayoutContext context, + LayoutContext childLC, int alignment) { + return getNextChildElements(childLM, context, childLC, alignment, null, null, null); + } + + private List getNextChildElements(LayoutManager childLM, LayoutContext context, + LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, + LayoutManager restartAtLM) { + childLC.copyPendingMarksFrom(context); + childLC.setStackLimitBP(context.getStackLimitBP()); + if (childLM instanceof LineLayoutManager) { + childLC.setRefIPD(getContentAreaIPD()); + } else { + childLC.setRefIPD(referenceIPD); + } + if (childLM == this.childLMs.get(0)) { + childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE); + //Handled already by the parent (break collapsing, see above) + } + + if (lmStack == null) { + return childLM.getNextKnuthElements(childLC, alignment); + } else { + if (childLM instanceof LineLayoutManager) { + return ((LineLayoutManager) childLM).getNextKnuthElements(childLC, alignment, + (LeafPosition) restartPosition); + } else { + return childLM.getNextKnuthElements(childLC, alignment, + lmStack, restartPosition, restartAtLM); + } + } } /** * Adds a break element to the content list between individual child elements. - * @param contentList the content list to populate - * @param context the current layout context + * @param contentList + * @param parentLC * @param childLC the currently active child layout context */ - protected void addInBetweenBreak(List contentList, LayoutContext context, - LayoutContext childLC) { + protected void addInBetweenBreak(List contentList, LayoutContext parentLC, + LayoutContext childLC) { + if (mustKeepTogether() - || context.isKeepWithNextPending() + || parentLC.isKeepWithNextPending() || childLC.isKeepWithPreviousPending()) { - int strength = getKeepTogetherStrength(); + Keep keep = getKeepTogether(); //Handle pending keep-with-next - strength = Math.max(strength, context.getKeepWithNextPending()); - context.clearKeepWithNextPending(); + keep = keep.compare(parentLC.getKeepWithNextPending()); + parentLC.clearKeepWithNextPending(); //Handle pending keep-with-previous from child LM - strength = Math.max(strength, childLC.getKeepWithPreviousPending()); + keep = keep.compare(childLC.getKeepWithPreviousPending()); childLC.clearKeepWithPreviousPending(); - int penalty = KeepUtil.getPenaltyForKeep(strength); - // add a penalty to forbid or discourage a break between blocks contentList.add(new BreakElement( - new Position(this), penalty, context)); + new Position(this), keep.getPenalty(), + keep.getContext(), parentLC)); return; } @@ -481,7 +637,7 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager // add a null penalty to allow a break between blocks contentList.add(new BreakElement( - new Position(this), 0, context)); + new Position(this), 0, Constants.EN_AUTO, parentLC)); } } @@ -817,33 +973,77 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager * Retrieves and returns the keep-together strength from the parent element. * @return the keep-together strength */ - protected int getParentKeepTogetherStrength() { - int strength = KEEP_AUTO; + protected Keep getParentKeepTogether() { + Keep keep = Keep.KEEP_AUTO; if (getParent() instanceof BlockLevelLayoutManager) { - strength = ((BlockLevelLayoutManager)getParent()).getKeepTogetherStrength(); + keep = ((BlockLevelLayoutManager)getParent()).getKeepTogether(); } else if (getParent() instanceof InlineLayoutManager) { if (((InlineLayoutManager) getParent()).mustKeepTogether()) { - strength = KEEP_ALWAYS; + keep = Keep.KEEP_ALWAYS; } //TODO Fix me //strength = ((InlineLayoutManager) getParent()).getKeepTogetherStrength(); } - return strength; + return keep; } /** {@inheritDoc} */ public boolean mustKeepTogether() { - return getKeepTogetherStrength() > KEEP_AUTO; + return !getKeepTogether().isAuto(); } /** {@inheritDoc} */ public boolean mustKeepWithPrevious() { - return getKeepWithPreviousStrength() > KEEP_AUTO; + return !getKeepWithPrevious().isAuto(); } /** {@inheritDoc} */ public boolean mustKeepWithNext() { - return getKeepWithNextStrength() > KEEP_AUTO; + return !getKeepWithNext().isAuto(); + } + + /** {@inheritDoc} */ + public Keep getKeepTogether() { + Keep keep = Keep.getKeep(getKeepTogetherProperty()); + keep = keep.compare(getParentKeepTogether()); + return keep; + } + + /** {@inheritDoc} */ + public Keep getKeepWithPrevious() { + return Keep.getKeep(getKeepWithPreviousProperty()); + } + + /** {@inheritDoc} */ + public Keep getKeepWithNext() { + return Keep.getKeep(getKeepWithNextProperty()); + } + + /** + * {@inheritDoc} + * Default implementation throws {@code IllegalStateException} + * Must be implemented by the subclass, if applicable. + */ + public KeepProperty getKeepTogetherProperty() { + throw new IllegalStateException(); + } + + /** + * {@inheritDoc} + * Default implementation throws {@code IllegalStateException} + * Must be implemented by the subclass, if applicable. + */ + public KeepProperty getKeepWithPreviousProperty() { + throw new IllegalStateException(); + } + + /** + * {@inheritDoc} + * Default implementation throws {@code IllegalStateException} + * Must be implemented by the subclass, if applicable. + */ + public KeepProperty getKeepWithNextProperty() { + throw new IllegalStateException(); } /** @@ -1608,5 +1808,13 @@ public abstract class BlockStackingLayoutManager extends AbstractLayoutManager return -1; } + /** {@inheritDoc} */ + public void reset() { + super.reset(); + breakBeforeServed = false; + firstVisibleMarkServed = false; + // TODO startIndent, endIndent + } + } diff --git a/src/java/org/apache/fop/layoutmgr/BreakElement.java b/src/java/org/apache/fop/layoutmgr/BreakElement.java index 98e31dbf8..3eb96e9cd 100644 --- a/src/java/org/apache/fop/layoutmgr/BreakElement.java +++ b/src/java/org/apache/fop/layoutmgr/BreakElement.java @@ -41,7 +41,22 @@ public class BreakElement extends UnresolvedListElement { * @param context the layout context which contains the pending conditional elements */ public BreakElement(Position position, int penaltyValue, LayoutContext context) { - this(position, 0, penaltyValue, -1, context); + this(position, penaltyValue, -1, context); + } + + /** + * Create a new BreakElement for the given {@code position}, {@code penaltyValue} + * and {@code breakClass}. (Used principally to generate break-possibilities in + * ranges of content that must be kept together within the context corresponding + * to the {@code breakClass}; expected to be one of {@link Constants#EN_AUTO}, + * {@link Constants#EN_LINE}, {@link Constants#EN_COLUMN} or {@link Constants#EN_PAGE}) + * @param position the corresponding {@link Position} + * @param penaltyValue the penalty value + * @param breakClass the break class + * @param context the {@link LayoutContext} + */ + public BreakElement(Position position, int penaltyValue, int breakClass, LayoutContext context) { + this(position, 0, penaltyValue, breakClass, context); } /** @@ -65,6 +80,10 @@ public class BreakElement extends UnresolvedListElement { this.pendingAfterMarks = context.getPendingAfterMarks(); } + private static String getBreakClassName(int breakClass) { + return AbstractBreaker.getBreakClassName(breakClass); + } + /** {@inheritDoc} */ public boolean isConditional() { return false; //Does not really apply here @@ -143,27 +162,17 @@ public class BreakElement extends UnresolvedListElement { /** {@inheritDoc} */ public String toString() { - StringBuffer sb = new StringBuffer(); + StringBuffer sb = new StringBuffer(64); sb.append("BreakPossibility[p:"); - sb.append(this.penaltyValue); + sb.append(KnuthPenalty.valueOf(this.penaltyValue)); if (isForcedBreak()) { - sb.append(" (forced break"); - switch (getBreakClass()) { - case Constants.EN_PAGE: - sb.append(", page"); - break; - case Constants.EN_COLUMN: - sb.append(", column"); - break; - case Constants.EN_EVEN_PAGE: - sb.append(", even page"); - break; - case Constants.EN_ODD_PAGE: - sb.append(", odd page"); - break; - default: - } - sb.append(")"); + sb.append(" (forced break, ") + .append(getBreakClassName(this.breakClass)) + .append(")"); + } else if (this.penaltyValue >= 0 && this.breakClass != -1) { + sb.append(" (keep constraint, ") + .append(getBreakClassName(this.breakClass)) + .append(")"); } sb.append("; w:"); sb.append(penaltyWidth); diff --git a/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java index 0bf228e7e..3a688cce8 100644 --- a/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java +++ b/src/java/org/apache/fop/layoutmgr/BreakingAlgorithm.java @@ -22,12 +22,12 @@ package org.apache.fop.layoutmgr; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.fo.FONode; +import org.apache.fop.fo.Constants; /** * The set of nodes is sorted into lines indexed into activeLines. * The nodes in each line are linked together in a single linked list by the - * KnuthNode.next field. The activeLines array contains a link to the head of + * {@link KnuthNode#next} field. The activeLines array contains a link to the head of * the linked list in index 'line*2' and a link to the tail at index 'line*2+1'. * <p> * The set of active nodes can be traversed by @@ -57,13 +57,42 @@ public abstract class BreakingAlgorithm { /** wrap-option = "no-wrap". */ public static final int ONLY_FORCED_BREAKS = 2; + /** Holder for symbolic literals for the fitness classes */ + static final class FitnessClasses { + static final int VERY_TIGHT = 0; + static final int TIGHT = 1; + static final int LOOSE = 2; + static final int VERY_LOOSE = 3; + + static final String[] NAMES = { "VERY TIGHT", "TIGHT", "LOOSE", "VERY LOOSE" }; + + /** + * Figure out the fitness class of this line (tight, loose, + * very tight or very loose). + * See the section on "More Bells and Whistles" in Knuth's + * "Breaking Paragraphs Into Lines". + * + * @param adjustRatio the adjustment ratio + * @return the fitness class + */ + static int computeFitness(double adjustRatio) { + if (adjustRatio < -0.5) { + return FitnessClasses.VERY_TIGHT; + } else if (adjustRatio <= 0.5) { + return FitnessClasses.TIGHT; + } else if (adjustRatio <= 1.0) { + return FitnessClasses.LOOSE; + } else { + return FitnessClasses.VERY_LOOSE; + } + } + } + // parameters of Knuth's algorithm: - /** Penalty value for flagged penalties. */ - private int flaggedPenalty = 50; /** Demerit for consecutive lines ending at flagged penalties. */ - protected int repeatedFlaggedDemerit = 50; + protected int repeatedFlaggedDemerit = KnuthPenalty.FLAGGED_PENALTY; /** Demerit for consecutive lines belonging to incompatible fitness classes . */ - protected int incompatibleFitnessDemerit = 50; + protected int incompatibleFitnessDemerit = KnuthPenalty.FLAGGED_PENALTY; /** Maximum number of consecutive lines ending with a flagged penalty. * Only a value >= 1 is a significant limit. */ @@ -110,7 +139,7 @@ public abstract class BreakingAlgorithm { /** Alignment of the paragraph's last line. */ protected int alignmentLast; /** Used to handle the text-indent property (indent the first line of a paragraph). */ - protected boolean bFirst; + protected boolean indentFirstPart; /** * The set of active nodes in ascending line order. For each line l, activeLines[2l] contains a @@ -151,30 +180,35 @@ public abstract class BreakingAlgorithm { protected BestRecords best; - /** {@inheritDoc} */ private boolean partOverflowRecoveryActivated = true; private KnuthNode lastRecovered; /** * Create a new instance. - * @param align alignment of the paragraph/page. One of EN_START, EN_JUSTIFY, etc. For - * pages EN_BEFORE, EN_AFTER are mapped to the corresponding inline properties - * (EN_START, EN_END) + * + * @param align alignment of the paragraph/page. One of {@link Constants#EN_START}, + * {@link Constants#EN_JUSTIFY}, {@link Constants#EN_CENTER}, + * {@link Constants#EN_END}. + * For pages, {@link Constants#EN_BEFORE} and {@link Constants#EN_AFTER} + * are mapped to the corresponding inline properties, + * {@link Constants#EN_START} and {@link Constants#EN_END}. * @param alignLast alignment of the paragraph's last line - * @param first for the text-indent property (indent the first line of a paragraph) - * @param partOverflowRecovery true if too long elements should be moved to the next line/part - * @param maxFlagCount maximum allowed number of consecutive lines ending at a flagged penalty - * item + * @param first for the text-indent property ({@code true} if the first line + * of a paragraph should be indented) + * @param partOverflowRecovery {@code true} if too long elements should be moved to + * the next line/part + * @param maxFlagCount maximum allowed number of consecutive lines ending at a flagged penalty + * item */ public BreakingAlgorithm(int align, int alignLast, boolean first, boolean partOverflowRecovery, int maxFlagCount) { - alignment = align; - alignmentLast = alignLast; - bFirst = first; + this.alignment = align; + this.alignmentLast = alignLast; + this.indentFirstPart = first; this.partOverflowRecoveryActivated = partOverflowRecovery; this.best = new BestRecords(); - maxFlaggedPenaltiesCount = maxFlagCount; + this.maxFlaggedPenaltiesCount = maxFlagCount; } @@ -183,34 +217,34 @@ public abstract class BreakingAlgorithm { */ public class KnuthNode { /** index of the breakpoint represented by this node */ - public int position; + public final int position; /** number of the line ending at this breakpoint */ - public int line; + public final int line; /** fitness class of the line ending at this breakpoint. One of 0, 1, 2, 3. */ - public int fitness; + public final int fitness; /** accumulated width of the KnuthElements up to after this breakpoint. */ - public int totalWidth; + public final int totalWidth; /** accumulated stretchability of the KnuthElements up to after this breakpoint. */ - public int totalStretch; + public final int totalStretch; /** accumulated shrinkability of the KnuthElements up to after this breakpoint. */ - public int totalShrink; + public final int totalShrink; /** adjustment ratio if the line ends at this breakpoint */ - public double adjustRatio; + public final double adjustRatio; /** available stretch of the line ending at this breakpoint */ - public int availableShrink; + public final int availableShrink; /** available shrink of the line ending at this breakpoint */ - public int availableStretch; + public final int availableStretch; /** difference between target and actual line width */ - public int difference; + public final int difference; /** minimum total demerits up to this breakpoint */ public double totalDemerits; @@ -249,7 +283,8 @@ public abstract class BreakingAlgorithm { return "<KnuthNode at " + position + " " + totalWidth + "+" + totalStretch + "-" + totalShrink + " line:" + line + " prev:" + (previous != null ? previous.position : -1) - + " dem:" + totalDemerits + ">"; + + " dem:" + totalDemerits + + " fitness:" + FitnessClasses.NAMES[fitness] + ">"; } } @@ -258,7 +293,6 @@ public abstract class BreakingAlgorithm { */ protected class BestRecords { private static final double INFINITE_DEMERITS = Double.POSITIVE_INFINITY; - //private static final double INFINITE_DEMERITS = 1E11; private double[] bestDemerits = new double[4]; private KnuthNode[] bestNode = new KnuthNode[4]; @@ -333,7 +367,7 @@ public abstract class BreakingAlgorithm { return bestAvailableStretch[fitness]; } - public int getDifference(int fitness) { + public int getDifference(int fitness) { return bestDifference[fitness]; } @@ -373,20 +407,21 @@ public abstract class BreakingAlgorithm { return this.partOverflowRecoveryActivated; } - /** Empty method, hook for subclasses. Called before determining the optimal + /** + * Empty method, hook for subclasses. Called before determining the optimal * breakpoints corresponding to a given active node. * @param total number of lines for the active node * @param demerits total demerits of the paragraph for the active node */ public abstract void updateData1(int total, double demerits); - /** Empty method, hook for subclasses. Called when determining the optimal breakpoints + /** + * Empty method, hook for subclasses. Called when determining the optimal breakpoints * for a given active node. * @param bestActiveNode a node in the chain of best active nodes, corresponding to * one of the optimal breakpoints * @param sequence the corresponding paragraph * @param total the number of lines into which the paragraph will be broken - * @see #calculateBreakPoints(KnuthNode, KnuthSequence, int) */ public abstract void updateData2(KnuthNode bestActiveNode, KnuthSequence sequence, @@ -404,13 +439,18 @@ public abstract class BreakingAlgorithm { return findBreakingPoints(par, 0, threshold, force, allowedBreaks); } - /** Finds an optimal set of breakpoints for the given paragraph. - * @param par the paragraph to break - * @param startIndex index of the Knuth element at which the breaking must start - * @param threshold upper bound of the adjustment ratio - * @param force true if a set of breakpoints must be found even if there are no - * feasible ones - * @param allowedBreaks one of ONLY_FORCED_BREAKS, NO_FLAGGED_PENALTIES, ALL_BREAKS + /** + * Finds an optimal set of breakpoints for the given paragraph. + * + * @param par the paragraph to break + * @param startIndex index of the Knuth element at which the breaking must start + * @param threshold upper bound of the adjustment ratio + * @param force {@code true} if a set of breakpoints must be found, even + * if there are no feasible ones + * @param allowedBreaks the type(s) of breaks allowed. One of {@link #ONLY_FORCED_BREAKS}, + * {@link #NO_FLAGGED_PENALTIES} or {@link #ALL_BREAKS}. + * + * @return the number of effective breaks */ public int findBreakingPoints(KnuthSequence par, int startIndex, double threshold, boolean force, @@ -418,142 +458,69 @@ public abstract class BreakingAlgorithm { this.par = par; this.threshold = threshold; this.force = force; - //this.lineWidth = lineWidth; - initialize(); - activeLines = new KnuthNode[20]; + // initialize the algorithm + initialize(); - // reset lastTooShort and lastTooLong, as they could be not null - // because of previous calls to findBreakingPoints - lastTooShort = lastTooLong = null; - // reset startLine and endLine - startLine = endLine = 0; - // current element in the paragraph - KnuthElement thisElement = null; // previous element in the paragraph is a KnuthBox? boolean previousIsBox = false; - // index of the first KnuthBox in the sequence + // index of the first KnuthBox in the sequence, in case of non-centered + // alignment. For centered alignment, we need to take into account preceding + // penalties+glues used for the filler spaces int firstBoxIndex = startIndex; - if (alignment != org.apache.fop.fo.Constants.EN_CENTER) { - while (par.size() > firstBoxIndex - && !((KnuthElement) par.get(firstBoxIndex)).isBox()) { - firstBoxIndex++; - } + if (alignment != Constants.EN_CENTER) { + firstBoxIndex = par.getFirstBoxIndex(startIndex); } + firstBoxIndex = (firstBoxIndex < 0) ? 0 : firstBoxIndex; // create an active node representing the starting point - activeLines = new KnuthNode[20]; addNode(0, createNode(firstBoxIndex, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null)); + KnuthNode lastForced = getNode(0); + if (log.isTraceEnabled()) { log.trace("Looping over " + (par.size() - startIndex) + " elements"); + log.trace(par); } - KnuthNode lastForced = getNode(0); - // main loop - for (int i = startIndex; i < par.size(); i++) { - thisElement = getElement(i); - if (thisElement.isBox()) { - // a KnuthBox object is not a legal line break - totalWidth += thisElement.getW(); - previousIsBox = true; - handleBox((KnuthBox) thisElement); - } else if (thisElement.isGlue()) { - // a KnuthGlue object is a legal line break - // only if the previous object is a KnuthBox - // consider these glues according to the value of allowedBreaks - if (previousIsBox - && !(allowedBreaks == ONLY_FORCED_BREAKS)) { - considerLegalBreak(thisElement, i); - } - totalWidth += thisElement.getW(); - totalStretch += thisElement.getY(); - totalShrink += thisElement.getZ(); - previousIsBox = false; - } else { - // a KnuthPenalty is a legal line break - // only if its penalty is not infinite; - // consider all penalties, non-flagged penalties or non-forcing penalties - // according to the value of allowedBreaks - if (((KnuthPenalty) thisElement).getP() < KnuthElement.INFINITE - && (!(allowedBreaks == NO_FLAGGED_PENALTIES) - || !(((KnuthPenalty) thisElement).isFlagged())) - && (!(allowedBreaks == ONLY_FORCED_BREAKS) - || ((KnuthPenalty) thisElement).getP() == -KnuthElement.INFINITE)) { - considerLegalBreak(thisElement, i); - } - previousIsBox = false; - } + for (int elementIndex = startIndex; elementIndex < par.size(); elementIndex++) { + + previousIsBox = handleElementAt( + elementIndex, previousIsBox, allowedBreaks).isBox(); + if (activeNodeCount == 0) { + if (ipdChanged()) { + return handleIpdChange(); + } if (!force) { log.debug("Could not find a set of breaking points " + threshold); return 0; } + // lastDeactivated was a "good" break, while lastTooShort and lastTooLong // were "bad" breaks since the beginning; // if it is not the node we just restarted from, lastDeactivated can // replace either lastTooShort or lastTooLong - if (lastDeactivated != null && lastDeactivated != lastForced) { - if (lastDeactivated.adjustRatio > 0) { - lastTooShort = lastDeactivated; - } else { - lastTooLong = lastDeactivated; - } + if (lastDeactivated != null + && lastDeactivated != lastForced) { + replaceLastDeactivated(); } - if (lastTooShort == null || lastForced.position == lastTooShort.position) { - if (isPartOverflowRecoveryActivated()) { - if (this.lastRecovered == null) { - this.lastRecovered = lastTooLong; - if (log.isDebugEnabled()) { - log.debug("Recovery point: " + lastRecovered); - } - } - // content would overflow, insert empty line/page and try again - KnuthNode node = createNode( - lastTooLong.previous.position, lastTooLong.previous.line + 1, 1, - 0, 0, 0, - 0, 0, 0, - 0, 0, lastTooLong.previous); - lastForced = node; - node.fitRecoveryCounter = lastTooLong.previous.fitRecoveryCounter + 1; - if (log.isDebugEnabled()) { - log.debug("first part doesn't fit into line, recovering: " - + node.fitRecoveryCounter); - } - if (node.fitRecoveryCounter > getMaxRecoveryAttempts()) { - while (lastForced.fitRecoveryCounter > 0) { - lastForced = lastForced.previous; - lastDeactivated = lastForced.previous; - startLine--; - endLine--; - } - lastForced = this.lastRecovered; - this.lastRecovered = null; - startLine = lastForced.line; - endLine = lastForced.line; - log.debug("rolled back..."); - } - } else { - lastForced = lastTooLong; - } + + if (lastTooShort == null + || lastForced.position == lastTooShort.position) { + lastForced = recoverFromOverflow(); } else { lastForced = lastTooShort; this.lastRecovered = null; } - - if (log.isDebugEnabled()) { - log.debug("Restarting at node " + lastForced); - } - i = restartFrom(lastForced, i); + elementIndex = restartFrom(lastForced, elementIndex); } + } + finish(); - if (log.isTraceEnabled()) { - log.trace("Main loop completed " + activeNodeCount); - log.trace("Active nodes=" + toString("")); - } // there is at least one set of breaking points // select one or more active nodes, removing the others from the list @@ -571,42 +538,49 @@ public abstract class BreakingAlgorithm { return line; } + protected boolean ipdChanged() { + return false; + } + + protected int handleIpdChange() { + throw new IllegalStateException(); + } + /** - * This method tries to find the context FO for a position in a KnuthSequence. - * @param seq the KnuthSequence to inspect - * @param position the index of the position in the KnuthSequence - * @return the requested context FO note or null, if no context node could be determined + * Recover from a {@link KnuthNode} leading to a line that is too long. + * The default implementation creates a new node corresponding to a break + * point after the previous node that led to a line that was too short. + * + * @param lastTooLong the node that leads to a "too long" line + * @return node corresponding to a breakpoint after the previous "too short" line */ - private FONode findContextFO(KnuthSequence seq, int position) { - ListElement el = seq.getElement(position); - while (el.getLayoutManager() == null && position < seq.size() - 1) { - position++; - el = seq.getElement(position); - } - Position pos = (el != null ? el.getPosition() : null); - LayoutManager lm = (pos != null ? pos.getLM() : null); - while (pos instanceof NonLeafPosition) { - pos = ((NonLeafPosition)pos).getPosition(); - if (pos != null && pos.getLM() != null) { - lm = pos.getLM(); - } - } - if (lm != null) { - return lm.getFObj(); - } else { - return null; + protected KnuthNode recoverFromTooLong(KnuthNode lastTooLong) { + if (log.isDebugEnabled()) { + log.debug("Recovering from too long: " + lastTooLong); } + + // content would overflow, insert empty line/page and try again + return createNode( + lastTooLong.previous.position, lastTooLong.previous.line + 1, 1, + 0, 0, 0, + 0, 0, 0, + 0, 0, lastTooLong.previous); } - /** Resets the algorithm's variables. */ + /** Initializes the algorithm's variables. */ protected void initialize() { this.totalWidth = 0; this.totalStretch = 0; this.totalShrink = 0; + this.lastTooShort = this.lastTooLong = null; + this.startLine = this.endLine = 0; + this.activeLines = new KnuthNode[20]; } - /** Creates a new active node for a feasible breakpoint at the given position. Only + /** + * Creates a new active node for a feasible breakpoint at the given position. Only * called in forced mode. + * * @param position index of the element in the Knuth sequence * @param line number of the line ending at the breakpoint * @param fitness fitness class of the line ending at the breakpoint. One of 0, 1, 2, 3. @@ -621,6 +595,7 @@ public abstract class BreakingAlgorithm { * @param difference difference between target and actual line width * @param totalDemerits minimum total demerits up to the breakpoint * @param previous active node for the preceding breakpoint + * @return a new node */ protected KnuthNode createNode(int position, int line, int fitness, int totalWidth, int totalStretch, int totalShrink, @@ -646,11 +621,173 @@ public abstract class BreakingAlgorithm { best.getNode(fitness)); } - /** Empty method, hook for subclasses. */ + /** + * Return the last node that yielded a too short line. + * @return the node corresponding to the last too short line + */ + protected final KnuthNode getLastTooShort() { + return this.lastTooShort; + } + + /** + * Generic handler for a {@link KnuthElement} at the given {@code position}, + * taking into account whether the preceding element was a box, and which + * type(s) of breaks are allowed. + * Non-overridable. This method simply serves to route the call to one of the + * more specific handlers ({@link #handleBox(KnuthBox)}, + * {@link #handleGlueAt(KnuthGlue,int,boolean,int)} or + * {@link #handlePenaltyAt(KnuthPenalty,int,int)}. The specialized handlers + * can be overridden by subclasses to add to or modify the default behavior + * for the different types of elements. + * + * @param position the position index of the element in the paragraph + * @param previousIsBox {@code true} if the previous element is a box + * @param allowedBreaks the type(s) of breaks allowed; should be one + * of {@link #ALL_BREAKS}, {@link #NO_FLAGGED_PENALTIES} + * or {@link #ONLY_FORCED_BREAKS} + * @return the handled element + */ + protected final KnuthElement handleElementAt(int position, + boolean previousIsBox, + int allowedBreaks) { + KnuthElement element = getElement(position); + if (element.isBox()) { + handleBox((KnuthBox) element); + } else if (element.isGlue()) { + handleGlueAt((KnuthGlue) element, position, previousIsBox, allowedBreaks); + } else if (element.isPenalty()){ + handlePenaltyAt((KnuthPenalty) element, position, allowedBreaks); + } else { + throw new IllegalArgumentException( + "Unknown KnuthElement type: expecting KnuthBox, KnuthGlue or KnuthPenalty"); + } + return element; + } + + /** + * Handle a {@link KnuthBox}. + * <br/><em>Note: default implementation just adds the box's width + * to the total content width. Subclasses that do not keep track + * of this themselves, but override this method, should remember + * to call {@code super.handleBox(box)} to avoid unwanted side-effects.</em> + * + * @param box the {@link KnuthBox} to handle + */ protected void handleBox(KnuthBox box) { + // a KnuthBox object is not a legal line break, + // just add the width to the total + totalWidth += box.getW(); + } + + /** + * Handle a {@link KnuthGlue} at the given position, + * taking into account the additional parameters. + * + * @param glue the {@link KnuthGlue} to handle + * @param position the position of the glue in the list + * @param previousIsBox {@code true} if the preceding element is a box + * @param allowedBreaks the type of breaks that are allowed + */ + protected void handleGlueAt(KnuthGlue glue, int position, + boolean previousIsBox, int allowedBreaks) { + // a KnuthGlue object is a legal line break + // only if the previous object is a KnuthBox + // consider these glues according to the value of allowedBreaks + if (previousIsBox + && !(allowedBreaks == ONLY_FORCED_BREAKS)) { + considerLegalBreak(glue, position); + } + totalWidth += glue.getW(); + totalStretch += glue.getY(); + totalShrink += glue.getZ(); + } + + /** + * Handle a {@link KnuthPenalty} at the given position, + * taking into account the type of breaks allowed. + * + * @param penalty the {@link KnuthPenalty} to handle + * @param position the position of the penalty in the list + * @param allowedBreaks the type of breaks that are allowed + */ + protected void handlePenaltyAt(KnuthPenalty penalty, int position, + int allowedBreaks) { + // a KnuthPenalty is a legal line break + // only if its penalty is not infinite; + // consider all penalties, non-flagged penalties or non-forcing penalties + // according to the value of allowedBreaks + if (((penalty.getP() < KnuthElement.INFINITE) + && (!(allowedBreaks == NO_FLAGGED_PENALTIES) || !penalty.isFlagged()) + && (!(allowedBreaks == ONLY_FORCED_BREAKS) + || penalty.isForcedBreak()))) { + considerLegalBreak(penalty, position); + } + } + + /** + * Replace the last too-long or too-short node by the last deactivated + * node, if applicable. + */ + protected final void replaceLastDeactivated() { + if (lastDeactivated.adjustRatio > 0) { + //last deactivated was too short + lastTooShort = lastDeactivated; + } else { + //last deactivated was too long or exactly the right width + lastTooLong = lastDeactivated; + } + } + + /** + * Recover from an overflow condition. + * + * @return the new {@code lastForced} node + */ + protected KnuthNode recoverFromOverflow() { + KnuthNode lastForced; + if (isPartOverflowRecoveryActivated()) { + if (lastRecovered == null) { + lastRecovered = lastTooLong; + if (log.isDebugEnabled()) { + log.debug("Recovery point: " + lastRecovered); + } + } + KnuthNode node = recoverFromTooLong(lastTooLong); + lastForced = node; + node.fitRecoveryCounter = lastTooLong.previous.fitRecoveryCounter + 1; + if (log.isDebugEnabled()) { + log.debug("first part doesn't fit into line, recovering: " + + node.fitRecoveryCounter); + } + if (node.fitRecoveryCounter > getMaxRecoveryAttempts()) { + while (lastForced.fitRecoveryCounter > 0 + && lastForced.previous != null) { + lastForced = lastForced.previous; + lastDeactivated = lastForced.previous; + } + lastForced = lastRecovered; + lastRecovered = null; + startLine = lastForced.line; + endLine = lastForced.line; + log.debug("rolled back..."); + } + } else { + lastForced = lastTooLong; + } + return lastForced; } + /** + * Restart from the given node at the given index. + * + * @param restartingNode the {@link KnuthNode} to restart from + * @param currentIndex the current position index + * @return the index of the restart point + */ protected int restartFrom(KnuthNode restartingNode, int currentIndex) { + if (log.isDebugEnabled()) { + log.debug("Restarting at node " + restartingNode); + } restartingNode.totalDemerits = 0; addNode(restartingNode.line, restartingNode); startLine = restartingNode.line; @@ -672,7 +809,8 @@ public abstract class BreakingAlgorithm { return restartingIndex; } - /** Determines if the given breakpoint is a feasible breakpoint. That is, if a decent + /** + * Determines if the given breakpoint is a feasible breakpoint. That is, if a decent * line may be built between one of the currently active nodes and this breakpoint. * @param element the paragraph's element to consider * @param elementIdx the element's index inside the paragraph @@ -694,9 +832,15 @@ public abstract class BreakingAlgorithm { continue; } int difference = computeDifference(node, element, elementIdx); + if (!elementCanEndLine(element, endLine, difference)) { + log.trace("Skipping legal break"); + break; + } + double r = computeAdjustmentRatio(node, difference); int availableShrink = totalShrink - node.totalShrink; int availableStretch = totalStretch - node.totalStretch; + if (log.isTraceEnabled()) { log.trace("\tr=" + r + " difference=" + difference); log.trace("\tline=" + line); @@ -704,87 +848,22 @@ public abstract class BreakingAlgorithm { // The line would be too long. if (r < -1 || element.isForcedBreak()) { - // Deactivate node. - if (log.isTraceEnabled()) { - log.trace("Removing " + node); - } - removeNode(line, node); - lastDeactivated = compareNodes(lastDeactivated, node); + deactivateNode(node, line); } + int fitnessClass = FitnessClasses.computeFitness(r); + double demerits = computeDemerits(node, element, fitnessClass, r); // The line is within the available shrink and the threshold. if (r >= -1 && r <= threshold) { - int fitnessClass = computeFitness(r); - double demerits = computeDemerits(node, element, fitnessClass, r); - - if (log.isTraceEnabled()) { - log.trace("\tDemerits=" + demerits); - log.trace("\tFitness class=" + fitnessClass); - } - - if (demerits < best.getDemerits(fitnessClass)) { - // updates best demerits data - best.addRecord(demerits, node, r, availableShrink, availableStretch, - difference, fitnessClass); - lastTooShort = null; - } + activateNode(node, difference, r, + demerits, fitnessClass, availableShrink, availableStretch); } - // The line is way too short, but we are in forcing mode, so a node is + // The line is way too short/long, but we are in forcing mode, so a node is // calculated and stored in lastValidNode. if (force && (r <= -1 || r > threshold)) { - int fitnessClass = computeFitness(r); - double demerits = computeDemerits(node, element, fitnessClass, r); - int newWidth = totalWidth; - int newStretch = totalStretch; - int newShrink = totalShrink; - - // add the width, stretch and shrink of glue elements after - // the break - // this does not affect the dimension of the line / page, only - // the values stored in the node; these would be as if the break - // was just before the next box element, thus ignoring glues and - // penalties between the "real" break and the following box - for (int i = elementIdx; i < par.size(); i++) { - KnuthElement tempElement = getElement(i); - if (tempElement.isBox()) { - break; - } else if (tempElement.isGlue()) { - newWidth += tempElement.getW(); - newStretch += tempElement.getY(); - newShrink += tempElement.getZ(); - } else if (tempElement.isForcedBreak() && i != elementIdx) { - break; - } - } - - if (r <= -1) { - if (lastTooLong == null || demerits < lastTooLong.totalDemerits) { - lastTooLong = createNode(elementIdx, line + 1, fitnessClass, - newWidth, newStretch, newShrink, - r, availableShrink, availableStretch, - difference, demerits, node); - if (log.isTraceEnabled()) { - log.trace("Picking tooLong " + lastTooLong); - } - } - } else { - if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) { - if (considerTooShort) { - //consider possibilities which are too short - best.addRecord(demerits, node, r, - availableShrink, availableStretch, - difference, fitnessClass); - } - lastTooShort = createNode(elementIdx, line + 1, fitnessClass, - newWidth, newStretch, newShrink, - r, availableShrink, availableStretch, - difference, demerits, node); - if (log.isTraceEnabled()) { - log.trace("Picking tooShort " + lastTooShort); - } - } - } + forceNode(node, line, elementIdx, difference, r, + demerits, fitnessClass, availableShrink, availableStretch); } } addBreaks(line, elementIdx); @@ -792,6 +871,145 @@ public abstract class BreakingAlgorithm { } /** + * Check if the given {@link KnuthElement} can end the line with the given + * number. + * @param element the element + * @param line the line number + * @param difference + * @return {@code true} if the element can end the line + */ + protected boolean elementCanEndLine(KnuthElement element, int line, int difference) { + return (!element.isPenalty() + || element.getP() < KnuthElement.INFINITE); + } + + /** + * Force the given {@link KnuthNode}, and register it. + * + * @param node the node + * @param line the line number + * @param elementIdx the position index of the element + * @param difference the difference between content-length and avaialable width + * @param r the adjustment ratio + * @param demerits demerits produced by the node + * @param fitnessClass the fitness class + * @param availableShrink the available amount of shrink + * @param availableStretch tha available amount of stretch + */ + protected void forceNode(KnuthNode node, + int line, + int elementIdx, + int difference, + double r, + double demerits, + int fitnessClass, + int availableShrink, + int availableStretch) { + + int newWidth = totalWidth; + int newStretch = totalStretch; + int newShrink = totalShrink; + + // add the width, stretch and shrink of glue elements after + // the break + // this does not affect the dimension of the line / page, only + // the values stored in the node; these would be as if the break + // was just before the next box element, thus ignoring glues and + // penalties between the "real" break and the following box + for (int i = elementIdx; i < par.size(); i++) { + KnuthElement tempElement = getElement(i); + if (tempElement.isBox()) { + break; + } else if (tempElement.isGlue()) { + newWidth += tempElement.getW(); + newStretch += tempElement.getY(); + newShrink += tempElement.getZ(); + } else if (tempElement.isForcedBreak() && i != elementIdx) { + break; + } + } + + if (r <= -1) { + log.debug("Considering tooLong, demerits=" + demerits); + if (lastTooLong == null || demerits < lastTooLong.totalDemerits) { + lastTooLong = createNode(elementIdx, line + 1, fitnessClass, + newWidth, newStretch, newShrink, + r, availableShrink, availableStretch, + difference, demerits, node); + if (log.isTraceEnabled()) { + log.trace("Picking tooLong " + lastTooLong); + } + } + } else { + if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) { + if (considerTooShort) { + //consider possibilities which are too short + best.addRecord(demerits, node, r, + availableShrink, availableStretch, + difference, fitnessClass); + } + lastTooShort = createNode(elementIdx, line + 1, fitnessClass, + newWidth, newStretch, newShrink, + r, availableShrink, availableStretch, + difference, demerits, node); + if (log.isTraceEnabled()) { + log.trace("Picking tooShort " + lastTooShort); + } + } + } + } + + /** + * Activate the given node. Will result in the given {@link KnuthNode} + * being registered as a feasible breakpoint, if the {@code demerits} are better + * than that of the best node registered for the given {@code fitnessClass}. + * + * @param node the node + * @param difference the difference between content-length and available width + * @param r the adjustment ratio + * @param demerits demerits produced by the node + * @param fitnessClass the fitness class + * @param availableShrink the available amount of shrink + * @param availableStretch the available amount of stretch + */ + protected void activateNode(KnuthNode node, + int difference, + double r, + double demerits, + int fitnessClass, + int availableShrink, + int availableStretch) { + + if (log.isTraceEnabled()) { + log.trace("\tDemerits=" + demerits); + log.trace("\tFitness class=" + FitnessClasses.NAMES[fitnessClass]); + } + + if (demerits < best.getDemerits(fitnessClass)) { + // updates best demerits data + best.addRecord(demerits, node, r, availableShrink, availableStretch, + difference, fitnessClass); + lastTooShort = null; + } + } + + /** + * Deactivate the given node + * + * @param node the node + * @param line the line number + */ + protected void deactivateNode(KnuthNode node, int line) { + // Deactivate node... + if (log.isTraceEnabled()) { + log.trace("Removing " + node); + } + removeNode(line, node); + // ... and remember it, if it was a good candidate + lastDeactivated = compareNodes(lastDeactivated, node); + } + + /** * Adds new active nodes for breaks at the given element. * @param line number of the previous line; this element will end line number (line+1) * @param elementIdx the element's index @@ -832,7 +1050,7 @@ public abstract class BreakingAlgorithm { // by line number and position; if (log.isTraceEnabled()) { log.trace("\tInsert new break in list of " + activeNodeCount - + " from fitness class " + i); + + " from fitness class " + FitnessClasses.NAMES[i]); } KnuthNode newNode = createNode(elementIdx, line + 1, i, newWidth, newStretch, newShrink); @@ -846,8 +1064,9 @@ public abstract class BreakingAlgorithm { * Return the difference between the natural width of a line that would be made * between the given active node and the given element, and the available width of the * real line. - * @param activeNode node for the previous breakpoint - * @param element currently considered breakpoint + * @param activeNode node for the previous breakpoint + * @param element currently considered breakpoint + * @param elementIndex index of the element that is considered as a breakpoint * @return The difference in width. Positive numbers mean extra space in the line, * negative number that the line overflows. */ @@ -862,7 +1081,7 @@ public abstract class BreakingAlgorithm { } /** - * Return the adjust ration needed to make up for the difference. A ration of + * Return the adjustment ratio needed to make up for the difference. A ratio of * <ul> * <li>0 means that the break has the exact right width</li> * <li>>= -1 && < 0 means that the break is wider than the line, @@ -871,9 +1090,9 @@ public abstract class BreakingAlgorithm { * but within the maximum values of the glues.</li> * <li>> 1 means that the break is too small to make up for the glues.</li> * </ul> - * @param activeNode - * @param difference - * @return The ration. + * @param activeNode the currently active node + * @param difference the difference between content-length and available width + * @return The adjustment ratio. */ protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) { // compute the adjustment ratio @@ -897,26 +1116,6 @@ public abstract class BreakingAlgorithm { } /** - * Figure out the fitness class of this line (tight, loose, - * very tight or very loose). - * See the section on "More Bells and Whistles" in Knuth's - * "Breaking Paragraphs Into Lines". - * @param r - * @return the fitness class - */ - private int computeFitness(double r) { - if (r < -0.5) { - return 0; - } else if (r <= 0.5) { - return 1; - } else if (r <= 1) { - return 2; - } else { - return 3; - } - } - - /** * Computes the demerits of the current breaking (that is, up to the given element), * if the next-to-last chosen breakpoint is the given active node. This adds to the * total demerits of the given active node, the demerits of a line starting at this @@ -933,12 +1132,16 @@ public abstract class BreakingAlgorithm { // compute demerits double f = Math.abs(r); f = 1 + 100 * f * f * f; - if (element.isPenalty() && element.getP() >= 0) { - f += element.getP(); - demerits = f * f; - } else if (element.isPenalty() && !element.isForcedBreak()) { + if (element.isPenalty()) { double penalty = element.getP(); - demerits = f * f - penalty * penalty; + if (penalty >= 0) { + f += penalty; + demerits = f * f; + } else if (!element.isForcedBreak()) { + demerits = f * f - penalty * penalty; + } else { + demerits = f * f; + } } else { demerits = f * f; } @@ -982,7 +1185,15 @@ public abstract class BreakingAlgorithm { return demerits; } + /** + * Hook for subclasses to trigger special behavior after ending the + * main loop in {@link #findBreakingPoints(KnuthSequence,int,double,boolean,int)} + */ protected void finish() { + if (log.isTraceEnabled()) { + log.trace("Main loop completed " + activeNodeCount); + log.trace("Active nodes=" + toString("")); + } } /** @@ -1083,12 +1294,8 @@ public abstract class BreakingAlgorithm { * @return the width/length in millipoints */ protected int getLineWidth(int line) { - if (this.lineWidth < 0) { - throw new IllegalStateException("lineWidth must be set" - + (this.lineWidth != 0 ? " and positive, but it is: " + this.lineWidth : "")); - } else { - return this.lineWidth; - } + assert lineWidth >= 0; + return this.lineWidth; } /** @return the constant line/part width or -1 if there is no such value */ @@ -1106,10 +1313,10 @@ public abstract class BreakingAlgorithm { sb.append("[\n"); for (int i = startLine; i < endLine; i++) { for (KnuthNode node = getNode(i); node != null; node = node.next) { - sb.append(prepend + "\t" + node + ",\n"); + sb.append(prepend).append('\t').append(node).append(",\n"); } } - sb.append(prepend + "]"); + sb.append(prepend).append("]"); return sb.toString(); } @@ -1121,7 +1328,7 @@ public abstract class BreakingAlgorithm { * @param par the corresponding paragraph * @param total the number of lines into which the paragraph will be broken */ - private void calculateBreakPoints(KnuthNode node, KnuthSequence par, + protected void calculateBreakPoints(KnuthNode node, KnuthSequence par, int total) { KnuthNode bestActiveNode = node; // use bestActiveNode to determine the optimum breakpoints diff --git a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java index dd23d2e85..67b9b4254 100644 --- a/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/FlowLayoutManager.java @@ -22,14 +22,14 @@ package org.apache.fop.layoutmgr; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.area.Area; import org.apache.fop.area.BlockParent; import org.apache.fop.fo.pagination.Flow; -import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager; -import org.apache.fop.layoutmgr.inline.WrapperLayoutManager; /** * LayoutManager for an fo:flow object. @@ -63,108 +63,151 @@ public class FlowLayoutManager extends BlockStackingLayoutManager /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { - // set layout dimensions - int flowIPD = getCurrentPV().getCurrentSpan().getColumnWidth(); - int flowBPD = getCurrentPV().getBodyRegion().getBPD(); - - // currently active LM - LayoutManager curLM; - List returnedList; - List returnList = new LinkedList(); + List elements = new LinkedList(); - while ((curLM = getChildLM()) != null) { - if (!(curLM instanceof WrapperLayoutManager) - && curLM instanceof InlineLevelLayoutManager) { - log.error("inline area not allowed under flow - ignoring"); - curLM.setFinished(true); - continue; + LayoutManager currentChildLM; + while ((currentChildLM = getChildLM()) != null) { + if (addChildElements(elements, currentChildLM, context, alignment) != null) { + return elements; } + } + + SpaceResolver.resolveElementList(elements); + setFinished(true); + + assert !elements.isEmpty(); + return elements; + } + + /** {@inheritDoc} */ + public List getNextKnuthElements(LayoutContext context, int alignment, + Position positionAtIPDChange, LayoutManager restartAtLM) { + + List elements = new LinkedList(); - int span = EN_NONE; - int disableColumnBalancing = EN_FALSE; - if (curLM instanceof BlockLayoutManager) { - span = ((BlockLayoutManager)curLM).getBlockFO().getSpan(); - disableColumnBalancing = ((BlockLayoutManager) curLM).getBlockFO() - .getDisableColumnBalancing(); - } else if (curLM instanceof BlockContainerLayoutManager) { - span = ((BlockContainerLayoutManager)curLM).getBlockContainerFO().getSpan(); - disableColumnBalancing = ((BlockContainerLayoutManager) curLM).getBlockContainerFO() - .getDisableColumnBalancing(); + LayoutManager currentChildLM = positionAtIPDChange.getLM(); + if (currentChildLM == null) { + throw new IllegalStateException( + "Cannot find layout manager from where to re-start layout after IPD change"); + } + if (restartAtLM != null && restartAtLM.getParent() == this) { + currentChildLM = restartAtLM; + setCurrentChildLM(currentChildLM); + currentChildLM.reset(); + if (addChildElements(elements, currentChildLM, context, alignment) != null) { + return elements; + } + } else { + Stack lmStack = new Stack(); + while (currentChildLM.getParent() != this) { + lmStack.push(currentChildLM); + currentChildLM = currentChildLM.getParent(); } + setCurrentChildLM(currentChildLM); + if (addChildElements(elements, currentChildLM, context, alignment, lmStack, + positionAtIPDChange, restartAtLM) != null) { + return elements; + } + } - int currentSpan = context.getCurrentSpan(); - if (currentSpan != span) { - if (span == EN_ALL) { - context.setDisableColumnBalancing(disableColumnBalancing); - } - log.debug("span change from " + currentSpan + " to " + span); - context.signalSpanChange(span); - SpaceResolver.resolveElementList(returnList); - return returnList; + while ((currentChildLM = getChildLM()) != null) { + currentChildLM.reset(); // TODO won't work with forced breaks + if (addChildElements(elements, currentChildLM, context, alignment) != null) { + return elements; } + } - // Set up a LayoutContext - //MinOptMax bpd = context.getStackLimit(); + SpaceResolver.resolveElementList(elements); + setFinished(true); - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimitBP(context.getStackLimitBP()); - childLC.setRefIPD(context.getRefIPD()); - childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode()); + assert !elements.isEmpty(); + return elements; + } - // get elements from curLM - returnedList = curLM.getNextKnuthElements(childLC, alignment); - //log.debug("FLM.getNextKnuthElements> returnedList.size() = " + returnedList.size()); - if (returnList.size() == 0 && childLC.isKeepWithPreviousPending()) { - context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); - childLC.clearKeepWithPreviousPending(); - } + private List addChildElements(List elements, LayoutManager childLM, LayoutContext context, + int alignment) { + return addChildElements(elements, childLM, context, alignment, null, null, null); + } - // "wrap" the Position inside each element - List tempList = returnedList; - returnedList = new LinkedList(); - wrapPositionElements(tempList, returnedList); - - if (returnedList.size() == 1 - && ElementListUtils.endsWithForcedBreak(returnedList)) { - // a descendant of this flow has break-before - returnList.addAll(returnedList); - SpaceResolver.resolveElementList(returnList); - return returnList; - } else if (returnedList.size() > 0) { - if (returnList.size() > 0 - && !ElementListUtils.startsWithForcedBreak(returnedList)) { - addInBetweenBreak(returnList, context, childLC); - } - returnList.addAll(returnedList); - if (ElementListUtils.endsWithForcedBreak(returnList)) { - if (curLM.isFinished() && !hasNextChildLM()) { - //If the layout manager is finished at this point, the pending - //marks become irrelevant. - childLC.clearPendingMarks(); - //setFinished(true); - break; - } - // a descendant of this flow has break-after - SpaceResolver.resolveElementList(returnList); - return returnList; - } + private List addChildElements(List elements, LayoutManager childLM, LayoutContext context, + int alignment, Stack lmStack, Position position, LayoutManager restartAtLM) { + if (handleSpanChange(childLM, elements, context)) { + SpaceResolver.resolveElementList(elements); + return elements; + } + + LayoutContext childLC = new LayoutContext(0); + List childrenElements = getNextChildElements(childLM, context, childLC, alignment, lmStack, + position, restartAtLM); + if (elements.isEmpty()) { + context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); + } + if (!elements.isEmpty() + && !ElementListUtils.startsWithForcedBreak(childrenElements)) { + addInBetweenBreak(elements, context, childLC); + } + context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); + + elements.addAll(childrenElements); + + if (ElementListUtils.endsWithForcedBreak(elements)) { + // a descendant of this flow has break-before or break-after + if (childLM.isFinished() && !hasNextChildLM()) { + setFinished(true); } + SpaceResolver.resolveElementList(elements); + return elements; + } + return null; + } - //Propagate and clear - context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); - childLC.clearKeepWithNextPending(); + private boolean handleSpanChange(LayoutManager childLM, List elements, LayoutContext context) { + int span = EN_NONE; + int disableColumnBalancing = EN_FALSE; + if (childLM instanceof BlockLayoutManager) { + span = ((BlockLayoutManager)childLM).getBlockFO().getSpan(); + disableColumnBalancing = ((BlockLayoutManager) childLM).getBlockFO() + .getDisableColumnBalancing(); + } else if (childLM instanceof BlockContainerLayoutManager) { + span = ((BlockContainerLayoutManager)childLM).getBlockContainerFO().getSpan(); + disableColumnBalancing = ((BlockContainerLayoutManager) childLM).getBlockContainerFO() + .getDisableColumnBalancing(); + } - context.updateKeepWithNextPending(getKeepWithNextStrength()); + int currentSpan = context.getCurrentSpan(); + if (currentSpan != span) { + if (span == EN_ALL) { + context.setDisableColumnBalancing(disableColumnBalancing); + } + log.debug("span change from " + currentSpan + " to " + span); + context.signalSpanChange(span); + return true; + } else { + return false; } + } - SpaceResolver.resolveElementList(returnList); - setFinished(true); + private List getNextChildElements(LayoutManager childLM, LayoutContext context, + LayoutContext childLC, int alignment, Stack lmStack, Position restartPosition, + LayoutManager restartLM) { + childLC.setStackLimitBP(context.getStackLimitBP()); + childLC.setRefIPD(context.getRefIPD()); + childLC.setWritingMode(getCurrentPage().getSimplePageMaster().getWritingMode()); - if (returnList.size() > 0) { - return returnList; + List childrenElements; + if (lmStack == null) { + childrenElements = childLM.getNextKnuthElements(childLC, alignment); } else { - return null; + childrenElements = childLM.getNextKnuthElements(childLC, + alignment, lmStack, restartPosition, restartLM); } + assert !childrenElements.isEmpty(); + + // "wrap" the Position inside each element + List tempList = childrenElements; + childrenElements = new LinkedList(); + wrapPositionElements(tempList, childrenElements); + return childrenElements; } /** @@ -203,18 +246,18 @@ public class FlowLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - return KEEP_AUTO; + public Keep getKeepTogether() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ @@ -352,5 +395,10 @@ public class FlowLayoutManager extends BlockStackingLayoutManager return getCurrentPV().getBodyRegion().getBPD(); } + /** {@inheritDoc} */ + public boolean isRestartable() { + return true; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/FootnoteBodyLayoutManager.java b/src/java/org/apache/fop/layoutmgr/FootnoteBodyLayoutManager.java index 791008aec..affa75abb 100644 --- a/src/java/org/apache/fop/layoutmgr/FootnoteBodyLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/FootnoteBodyLayoutManager.java @@ -92,18 +92,18 @@ public class FootnoteBodyLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - return getParentKeepTogetherStrength(); + public Keep getKeepTogether() { + return getParentKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; } } diff --git a/src/java/org/apache/fop/layoutmgr/InlineKnuthSequence.java b/src/java/org/apache/fop/layoutmgr/InlineKnuthSequence.java index 6d11a3c24..104c71131 100644 --- a/src/java/org/apache/fop/layoutmgr/InlineKnuthSequence.java +++ b/src/java/org/apache/fop/layoutmgr/InlineKnuthSequence.java @@ -57,16 +57,12 @@ public class InlineKnuthSequence extends KnuthSequence { return true; } - /* (non-Javadoc) - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean canAppendSequence(KnuthSequence sequence) { return sequence.isInlineSequence() && !isClosed; } - /* (non-Javadoc) - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean appendSequence(KnuthSequence sequence) { if (!canAppendSequence(sequence)) { return false; @@ -83,18 +79,14 @@ public class InlineKnuthSequence extends KnuthSequence { return true; } - /* (non-Javadoc) - * {@inheritDoc} - */ + /** {@inheritDoc} */ public boolean appendSequence(KnuthSequence sequence, boolean keepTogether, BreakElement breakElement) { return appendSequence(sequence); } - /* (non-Javadoc) - * {@inheritDoc} - */ + /** {@inheritDoc} */ public KnuthSequence endSequence() { if (!isClosed) { add(new KnuthPenalty(0, -KnuthElement.INFINITE, false, null, false)); diff --git a/src/java/org/apache/fop/layoutmgr/Keep.java b/src/java/org/apache/fop/layoutmgr/Keep.java new file mode 100644 index 000000000..444448ae4 --- /dev/null +++ b/src/java/org/apache/fop/layoutmgr/Keep.java @@ -0,0 +1,152 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.layoutmgr; + +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.properties.KeepProperty; +import org.apache.fop.fo.properties.Property; + +/** + * Object representing a keep constraint, corresponding + * to the XSL-FO <a href="http://www.w3.org/TR/xsl/#d0e26492">keep properties</a>. + */ +public class Keep { + + /** The integer value for "auto" keep strength. */ + private static final int STRENGTH_AUTO = Integer.MIN_VALUE; + + /** The integer value for "always" keep strength. */ + private static final int STRENGTH_ALWAYS = Integer.MAX_VALUE; + + public static final Keep KEEP_AUTO = new Keep(STRENGTH_AUTO, Constants.EN_AUTO); + + public static final Keep KEEP_ALWAYS = new Keep(STRENGTH_ALWAYS, Constants.EN_LINE); + + private int strength; + + private int context; + + private Keep(int strength, int context) { + this.strength = strength; + this.context = context; + } + + private static int getKeepStrength(Property keep) { + if (keep.isAuto()) { + return STRENGTH_AUTO; + } else if (keep.getEnum() == Constants.EN_ALWAYS) { + return STRENGTH_ALWAYS; + } else { + return keep.getNumber().intValue(); + } + } + + /** + * Obtain a Keep instance corresponding to the given {@link KeepProperty} + * + * @param keepProperty the {@link KeepProperty} + * @return a new instance corresponding to the given property + */ + public static Keep getKeep(KeepProperty keepProperty) { + Keep keep = new Keep(STRENGTH_AUTO, Constants.EN_AUTO); + keep.update(keepProperty.getWithinPage(), Constants.EN_PAGE); + keep.update(keepProperty.getWithinColumn(), Constants.EN_COLUMN); + keep.update(keepProperty.getWithinLine(), Constants.EN_LINE); + return keep; + } + + private void update(Property keep, int context) { + if (!keep.isAuto()) { + this.strength = getKeepStrength(keep); + this.context = context; + } + } + + /** @return {@code true} if the keep property was specified as "auto" */ + public boolean isAuto() { + return strength == STRENGTH_AUTO; + } + + /** + * Returns the context of this keep. + * + * @return one of {@link Constants#EN_LINE}, {@link Constants#EN_COLUMN} or + * {@link Constants#EN_PAGE} + */ + public int getContext() { + return context; + } + + /** @return the penalty value corresponding to the strength of this Keep */ + public int getPenalty() { + if (strength == STRENGTH_AUTO) { + return 0; + } else if (strength == STRENGTH_ALWAYS) { + return KnuthElement.INFINITE; + } else { + return KnuthElement.INFINITE - 1; + } + } + + private static int getKeepContextPriority(int context) { + switch (context) { + case Constants.EN_LINE: return 0; + case Constants.EN_COLUMN: return 1; + case Constants.EN_PAGE: return 2; + case Constants.EN_AUTO: return 3; + default: throw new IllegalArgumentException(); + } + } + + /** + * Compare this Keep instance to another one, and return the + * stronger one if the context is the same + * + * @param other the instance to compare to + * @return the winning Keep instance + */ + public Keep compare(Keep other) { + + /* check strength "always" first, regardless of priority */ + if (this.strength == STRENGTH_ALWAYS + && this.strength > other.strength) { + return this; + } else if (other.strength == STRENGTH_ALWAYS + && other.strength > this.strength) { + return other; + } + + int pThis = getKeepContextPriority(this.context); + int pOther = getKeepContextPriority(other.context); + + /* equal priority: strongest wins */ + if (pThis == pOther) { + return (strength >= other.strength) ? this : other; + } + + /* different priority: lowest priority wins */ + return (pThis < pOther) ? this : other; + } + + /** {@inheritDoc} */ + public String toString() { + return (strength == STRENGTH_AUTO) ? "auto" + : (strength == STRENGTH_ALWAYS) ? "always" + : Integer.toString(strength); + } +} diff --git a/src/java/org/apache/fop/layoutmgr/KeepUtil.java b/src/java/org/apache/fop/layoutmgr/KeepUtil.java deleted file mode 100644 index 5cc33533f..000000000 --- a/src/java/org/apache/fop/layoutmgr/KeepUtil.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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. - */ - -/* $Id$ */ - -package org.apache.fop.layoutmgr; - -import org.apache.fop.fo.Constants; -import org.apache.fop.fo.properties.KeepProperty; -import org.apache.fop.fo.properties.Property; - -/** - * Utility class for working with keeps. - */ -public class KeepUtil { - - /** - * Converts a keep property into an integer value. - * <p> - * Note: The conversion restricts the effectively available integer range by two values. - * Integer.MIN_VALUE is used to represent the value "auto" and - * Integer.MAX_VALUE is used to represebt the value "always". - * @param keep the keep property - * @return the keep value as an integer - */ - public static int getKeepStrength(Property keep) { - if (keep.isAuto()) { - return BlockLevelLayoutManager.KEEP_AUTO; - } else if (keep.getEnum() == Constants.EN_ALWAYS) { - return BlockLevelLayoutManager.KEEP_ALWAYS; - } else { - return keep.getNumber().intValue(); - } - } - - /** - * Returns the combined block-level keep strength from a keep property. - * <p> - * Note: This is a temporary method to be used until it is possible to differentiate between - * page and column keeps! - * @param keep the keep property - * @return the combined keep strength - */ - public static int getCombinedBlockLevelKeepStrength(KeepProperty keep) { - return Math.max( - getKeepStrength(keep.getWithinPage()), - getKeepStrength(keep.getWithinColumn())); - } - - /** - * Indicates whether a keep strength indicates a keep constraint. - * @param strength the keep strength - * @return true if the keep is not "auto" - */ - public static boolean hasKeep(int strength) { - return strength > BlockLevelLayoutManager.KEEP_AUTO; - } - - /** - * Returns the penalty value to be used for a certain keep strength. - * <ul> - * <li>"auto": returns 0</li> - * <li>"always": returns KnuthElement.INFINITE</li> - * <li>otherwise: returns KnuthElement.INFINITE - 1</li> - * </ul> - * @param keepStrength the keep strength - * @return the penalty value - */ - public static int getPenaltyForKeep(int keepStrength) { - if (keepStrength == BlockLevelLayoutManager.KEEP_AUTO) { - return 0; - } - int penalty = KnuthElement.INFINITE; - if (keepStrength < BlockLevelLayoutManager.KEEP_ALWAYS) { - penalty--; - } - return penalty; - } - - /** - * Returns a string representation of a keep strength value. - * @param keepStrength the keep strength - * @return the string representation - */ - public static String keepStrengthToString(int keepStrength) { - if (keepStrength == BlockLevelLayoutManager.KEEP_AUTO) { - return "auto"; - } else if (keepStrength == BlockLevelLayoutManager.KEEP_ALWAYS) { - return "always"; - } else { - return Integer.toString(keepStrength); - } - } - -} diff --git a/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java b/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java index 6c13fba8a..5e44127df 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthPenalty.java @@ -45,7 +45,7 @@ public class KnuthPenalty extends KnuthElement { public static final int FLAGGED_PENALTY = 50; private int penalty; - private boolean bFlagged; + private boolean isFlagged; private int breakClass = -1; /** @@ -55,12 +55,12 @@ public class KnuthPenalty extends KnuthElement { * @param p the penalty value of this penalty * @param f is this penalty flagged? * @param pos the Position stored in this penalty - * @param bAux is this penalty auxiliary? + * @param isAuxiliary is this penalty auxiliary? */ - public KnuthPenalty(int w, int p, boolean f, Position pos, boolean bAux) { - super(w, pos, bAux); + public KnuthPenalty(int w, int p, boolean f, Position pos, boolean isAuxiliary) { + super(w, pos, isAuxiliary); penalty = p; - bFlagged = f; + isFlagged = f; } /** @@ -69,18 +69,37 @@ public class KnuthPenalty extends KnuthElement { * @param w the width of this penalty * @param p the penalty value of this penalty * @param f is this penalty flagged? - * @param iBreakClass the break class of this penalty (one of + * @param breakClass the break class of this penalty (one of * {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE}, * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}) * @param pos the Position stored in this penalty - * @param bAux is this penalty auxiliary? + * @param isAuxiliary is this penalty auxiliary? */ public KnuthPenalty(int w, int p, boolean f, - int iBreakClass, Position pos, boolean bAux) { - super(w, pos, bAux); - penalty = p; - bFlagged = f; - breakClass = iBreakClass; + int breakClass, Position pos, boolean isAuxiliary) { + this(w, p, f, pos, isAuxiliary); + this.breakClass = breakClass; + } + + private static String getBreakClassName(int breakClass) { + return AbstractBreaker.getBreakClassName(breakClass); + } + + /** + * Get the penalty's value as a {@code java.lang.String}. + * (Mainly used in {@code toString()} methods, to improve readability + * of the trace logs.) + * + * @param penaltyValue the penalty value + * @return the penalty value as a {@code java.lang.String} + */ + protected static String valueOf(int penaltyValue) { + String result = (penaltyValue < 0) ? "-" : ""; + int tmpValue = Math.abs(penaltyValue); + result += (tmpValue == KnuthElement.INFINITE) + ? "INFINITE" + : String.valueOf(tmpValue); + return result; } /** {@inheritDoc} */ @@ -105,7 +124,7 @@ public class KnuthPenalty extends KnuthElement { /** @return true is this penalty is a flagged one. */ public boolean isFlagged() { - return bFlagged; + return isFlagged; } /** {@inheritDoc} */ @@ -121,14 +140,6 @@ public class KnuthPenalty extends KnuthElement { return breakClass; } - /** - * Sets the break class for this penalty. - * @param cl the break class (EN_AUTO, EN_COLUMN, EN_PAGE, EN_EVEN_PAGE, EN_ODD_PAGE) - */ - public void setBreakClass(int cl) { - this.breakClass = cl; - } - /** {@inheritDoc} */ public String toString() { StringBuffer sb = new StringBuffer(64); @@ -137,39 +148,22 @@ public class KnuthPenalty extends KnuthElement { } sb.append("penalty"); sb.append(" p="); - if (getP() < 0) { - sb.append("-"); - } - if (Math.abs(getP()) == INFINITE) { - sb.append("INFINITE"); - } else { - sb.append(getP()); - } - if (isFlagged()) { + sb.append(valueOf(this.penalty)); + if (this.isFlagged) { sb.append(" [flagged]"); } sb.append(" w="); sb.append(getW()); if (isForcedBreak()) { - sb.append(" (forced break"); - switch (getBreakClass()) { - case Constants.EN_PAGE: - sb.append(", page"); - break; - case Constants.EN_COLUMN: - sb.append(", column"); - break; - case Constants.EN_EVEN_PAGE: - sb.append(", even page"); - break; - case Constants.EN_ODD_PAGE: - sb.append(", odd page"); - break; - default: - } - sb.append(")"); + sb.append(" (forced break, ") + .append(getBreakClassName(this.breakClass)) + .append(")"); + } else if (this.penalty >= 0 && this.breakClass != -1) { + //penalty corresponding to a keep constraint + sb.append(" (keep constraint, ") + .append(getBreakClassName(this.breakClass)) + .append(")"); } return sb.toString(); } - } diff --git a/src/java/org/apache/fop/layoutmgr/KnuthSequence.java b/src/java/org/apache/fop/layoutmgr/KnuthSequence.java index fe9a01498..feb633265 100644 --- a/src/java/org/apache/fop/layoutmgr/KnuthSequence.java +++ b/src/java/org/apache/fop/layoutmgr/KnuthSequence.java @@ -19,6 +19,8 @@ package org.apache.fop.layoutmgr; +import org.apache.fop.util.ListUtil; + import java.util.ArrayList; import java.util.List; import java.util.ListIterator; @@ -26,9 +28,6 @@ import java.util.ListIterator; /** * Represents a list of Knuth elements. */ -/** - * - */ public abstract class KnuthSequence extends ArrayList { /** * Creates a new and empty list. @@ -132,11 +131,9 @@ public abstract class KnuthSequence extends ArrayList { * @return the last element of this sequence. */ public ListElement getLast() { - int idx = size(); - if (idx == 0) { - return null; - } - return (ListElement) get(idx - 1); + return (isEmpty() + ? null + : (ListElement) ListUtil.getLast(this)); } /** @@ -144,11 +141,9 @@ public abstract class KnuthSequence extends ArrayList { * @return the removed element. */ public ListElement removeLast() { - int idx = size(); - if (idx == 0) { - return null; - } - return (ListElement) remove(idx - 1); + return (isEmpty() + ? null + : (ListElement) ListUtil.removeLast(this)); } /** @@ -156,7 +151,45 @@ public abstract class KnuthSequence extends ArrayList { * @return the element at index index. */ public ListElement getElement(int index) { - return (ListElement) get(index); + return (index >= size() || index < 0) + ? null + : (ListElement) get(index); + } + + /** @return the position index of the first box in this sequence */ + protected int getFirstBoxIndex() { + if (isEmpty()) { + return -1; + } else { + return getFirstBoxIndex(0); + } + } + + /** + * Get the position index of the first box in this sequence, + * starting at the given index. If there is no box after the + * passed {@code startIndex}, the starting index itself is returned. + * @param startIndex the starting index for the lookup + * @return the absolute position index of the next box element + */ + protected int getFirstBoxIndex(int startIndex) { + if (isEmpty() || startIndex < 0 || startIndex >= size()) { + return -1; + } else { + ListElement element = null; + int posIndex = startIndex; + int lastIndex = size(); + while (posIndex < lastIndex + && !(element = getElement(posIndex)).isBox()) { + posIndex++; + } + if (posIndex != startIndex + && element.isBox()) { + return posIndex - 1; + } else { + return startIndex; + } + } } /** @@ -165,4 +198,9 @@ public abstract class KnuthSequence extends ArrayList { */ public abstract boolean isInlineSequence(); + /** {@inheritDoc} */ + public String toString() { + return "<KnuthSequence " + super.toString() + ">"; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutContext.java b/src/java/org/apache/fop/layoutmgr/LayoutContext.java index 4d56d1657..81726e57b 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutContext.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutContext.java @@ -78,15 +78,6 @@ public class LayoutContext { * level LM to allow them to optimize returned break possibilities. */ private MinOptMax stackLimitBP; - /** - * Total available stacking dimension for a "galley-level" layout - * manager in inline-progression-direction. It is passed by the - * parent LM. For LineLM, the block LM determines this based on - * indent properties. - * These LM <b>may</b> wish to pass this information down to lower - * level LM to allow them to optimize returned break possibilities. - */ - private MinOptMax stackLimitIP; /** to keep track of spanning in multi-column layout */ private int currentSpan = Constants.NOT_SET; @@ -145,8 +136,8 @@ public class LayoutContext { private int breakBefore; private int breakAfter; - private int pendingKeepWithNext = BlockLevelLayoutManager.KEEP_AUTO; - private int pendingKeepWithPrevious = BlockLevelLayoutManager.KEEP_AUTO; + private Keep pendingKeepWithNext = Keep.KEEP_AUTO; + private Keep pendingKeepWithPrevious = Keep.KEEP_AUTO; private int disableColumnBalancing; @@ -158,7 +149,7 @@ public class LayoutContext { this.flags = parentLC.flags; this.refIPD = parentLC.refIPD; this.writingMode = parentLC.writingMode; - setStackLimitsFrom(parentLC); + setStackLimitBP(parentLC.getStackLimitBP()); this.leadingSpace = parentLC.leadingSpace; //??? this.trailingSpace = parentLC.trailingSpace; //??? this.hyphContext = parentLC.hyphContext; @@ -183,7 +174,6 @@ public class LayoutContext { this.flags = flags; this.refIPD = 0; stackLimitBP = new MinOptMax(0); - stackLimitIP = new MinOptMax(0); leadingSpace = null; trailingSpace = null; } @@ -237,7 +227,7 @@ public class LayoutContext { * Returns the strength of a keep-with-next currently pending. * @return the keep-with-next strength */ - public int getKeepWithNextPending() { + public Keep getKeepWithNextPending() { return this.pendingKeepWithNext; } @@ -245,7 +235,7 @@ public class LayoutContext { * Returns the strength of a keep-with-previous currently pending. * @return the keep-with-previous strength */ - public int getKeepWithPreviousPending() { + public Keep getKeepWithPreviousPending() { return this.pendingKeepWithPrevious; } @@ -253,14 +243,14 @@ public class LayoutContext { * Clears any pending keep-with-next strength. */ public void clearKeepWithNextPending() { - this.pendingKeepWithNext = BlockLevelLayoutManager.KEEP_AUTO; + this.pendingKeepWithNext = Keep.KEEP_AUTO; } /** * Clears any pending keep-with-previous strength. */ public void clearKeepWithPreviousPending() { - this.pendingKeepWithPrevious = BlockLevelLayoutManager.KEEP_AUTO; + this.pendingKeepWithPrevious = Keep.KEEP_AUTO; } /** @@ -273,18 +263,18 @@ public class LayoutContext { /** * Updates the currently pending keep-with-next strength. - * @param strength the new strength to consider + * @param keep the new strength to consider */ - public void updateKeepWithNextPending(int strength) { - this.pendingKeepWithNext = Math.max(this.pendingKeepWithNext, strength); + public void updateKeepWithNextPending(Keep keep) { + this.pendingKeepWithNext = this.pendingKeepWithNext.compare(keep); } /** * Updates the currently pending keep-with-previous strength. - * @param strength the new strength to consider + * @param keep the new strength to consider */ - public void updateKeepWithPreviousPending(int strength) { - this.pendingKeepWithPrevious = Math.max(this.pendingKeepWithPrevious, strength); + public void updateKeepWithPreviousPending(Keep keep) { + this.pendingKeepWithPrevious = this.pendingKeepWithPrevious.compare(keep); } /** @@ -292,7 +282,7 @@ public class LayoutContext { * @return true if a keep-with-next constraint is pending */ public boolean isKeepWithNextPending() { - return getKeepWithNextPending() != BlockLevelLayoutManager.KEEP_AUTO; + return !getKeepWithNextPending().isAuto(); } /** @@ -300,7 +290,7 @@ public class LayoutContext { * @return true if a keep-with-previous constraint is pending */ public boolean isKeepWithPreviousPending() { - return getKeepWithPreviousPending() != BlockLevelLayoutManager.KEEP_AUTO; + return !getKeepWithPreviousPending().isAuto(); } public void setLeadingSpace(SpaceSpecifier space) { @@ -398,31 +388,6 @@ public class LayoutContext { } /** - * Sets the stack limit in inline-progression-dimension. - * @param limit the stack limit - */ - public void setStackLimitIP(MinOptMax limit) { - stackLimitIP = limit; - } - - /** - * Returns the stack limit in inline-progression-dimension. - * @return the stack limit - */ - public MinOptMax getStackLimitIP() { - return stackLimitIP; - } - - /** - * Sets (Copies) the stack limits in both directions from another layout context. - * @param context the layout context to take the values from - */ - public void setStackLimitsFrom(LayoutContext context) { - setStackLimitBP(context.getStackLimitBP()); - setStackLimitIP(context.getStackLimitIP()); - } - - /** * Sets the inline-progression-dimension of the nearest ancestor reference area. */ public void setRefIPD(int ipd) { @@ -662,8 +627,6 @@ public class LayoutContext { return "Layout Context:" + "\nStack Limit BPD: \t" + (getStackLimitBP() == null ? "null" : getStackLimitBP().toString()) - + "\nStack Limit IPD: \t" - + (getStackLimitIP() == null ? "null" : getStackLimitIP().toString()) + "\nTrailing Space: \t" + (getTrailingSpace() == null ? "null" : getTrailingSpace().toString()) + "\nLeading Space: \t" @@ -677,9 +640,8 @@ public class LayoutContext { + "\nStarts New Area: \t" + startsNewArea() + "\nIs Last Area: \t" + isLastArea() + "\nTry Hyphenate: \t" + tryHyphenate() - + "\nKeeps: \t[keep-with-next=" + KeepUtil.keepStrengthToString(getKeepWithNextPending()) - + "][keep-with-previous=" - + KeepUtil.keepStrengthToString(getKeepWithPreviousPending()) + "] pending" + + "\nKeeps: \t[keep-with-next=" + getKeepWithNextPending() + + "][keep-with-previous=" + getKeepWithPreviousPending() + "] pending" + "\nBreaks: \tforced [" + (breakBefore != Constants.EN_AUTO ? "break-before" : "") + "][" + (breakAfter != Constants.EN_AUTO ? "break-after" : "") + "]"; } diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManager.java b/src/java/org/apache/fop/layoutmgr/LayoutManager.java index f19588a77..454b8b366 100644 --- a/src/java/org/apache/fop/layoutmgr/LayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/LayoutManager.java @@ -20,6 +20,7 @@ package org.apache.fop.layoutmgr; import java.util.List; +import java.util.Stack; import org.apache.fop.area.Area; import org.apache.fop.datatypes.PercentBaseContext; @@ -219,4 +220,36 @@ public interface LayoutManager extends PercentBaseContext { * @return the same Position but with a position index */ Position notifyPos(Position pos); + + /** + * Re-initializes this layout manager in order to re-generate its Knuth + * elements according to a new IPD value. + */ + void reset(); + + /** + * Returns {@code true} if this layout manager is able to re-generate its + * Knuth elements after an IPD change. + * + * @return {@code true} if this layout manager can be restarted after an IPD + * change + */ + boolean isRestartable(); + + /** + * Returns an updated list of Knuth elements corresponding to this layout + * manager, after a change of IPD has been detected. + * + * @param context the layout context + * @param alignment the alignment + * @param lmStack the stack of LMs that are active at the IPD change + * @param positionAtIPDChange the position corresponding to the element + * finishing the page before the IPD change + * @param restartAtLM if not null, the layout manager from which to restart. + * That is, the IPD change occurs between two block elements and not inside + * a paragraph + * @return an updated list of elements, taking the new IPD into account + */ + List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack, + Position positionAtIPDChange, LayoutManager restartAtLM); } diff --git a/src/java/org/apache/fop/layoutmgr/LeafPosition.java b/src/java/org/apache/fop/layoutmgr/LeafPosition.java index ed8cc94e2..8b2a5f4bc 100644 --- a/src/java/org/apache/fop/layoutmgr/LeafPosition.java +++ b/src/java/org/apache/fop/layoutmgr/LeafPosition.java @@ -21,15 +21,20 @@ package org.apache.fop.layoutmgr; public class LeafPosition extends Position { - private int iLeafPos; + private int leafPos; public LeafPosition(LayoutManager lm, int pos) { super(lm); - iLeafPos = pos; + leafPos = pos; + } + + public LeafPosition(LayoutManager layoutManager, int pos, int index) { + super(layoutManager, index); + leafPos = pos; } public int getLeafPos() { - return iLeafPos; + return leafPos; } public boolean generatesAreas() { diff --git a/src/java/org/apache/fop/layoutmgr/Page.java b/src/java/org/apache/fop/layoutmgr/Page.java index 7e22cef67..b183efa01 100644 --- a/src/java/org/apache/fop/layoutmgr/Page.java +++ b/src/java/org/apache/fop/layoutmgr/Page.java @@ -19,7 +19,7 @@ package org.apache.fop.layoutmgr; -import java.awt.geom.Rectangle2D; +import java.awt.Rectangle; import org.apache.fop.area.PageViewport; import org.apache.fop.fo.pagination.SimplePageMaster; @@ -54,7 +54,7 @@ public class Page { * @param pageNumberStr the page number (as a String) * @param blank true if this is a blank page */ - public Page(Rectangle2D viewArea, int pageNumber, String pageNumberStr, boolean blank) { + public Page(Rectangle viewArea, int pageNumber, String pageNumberStr, boolean blank) { this.spm = null; this.pageViewport = new PageViewport(viewArea, pageNumber, pageNumberStr, null, blank); } diff --git a/src/java/org/apache/fop/layoutmgr/PageBreaker.java b/src/java/org/apache/fop/layoutmgr/PageBreaker.java index 1137065ab..86c3fccf8 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreaker.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreaker.java @@ -28,7 +28,6 @@ import org.apache.fop.area.Footnote; import org.apache.fop.area.PageViewport; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; -import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.Region; import org.apache.fop.fo.pagination.RegionBody; import org.apache.fop.fo.pagination.StaticContent; @@ -78,6 +77,14 @@ public class PageBreaker extends AbstractBreaker { return pslm.getPageProvider(); } + /** + * Starts the page breaking process. + * @param flowBPD the constant available block-progression-dimension (used for every part) + */ + void doLayout(int flowBPD) { + doLayout(flowBPD, false); + } + /** {@inheritDoc} */ protected PageBreakingLayoutListener createLayoutListener() { return new PageBreakingLayoutListener() { @@ -122,6 +129,12 @@ public class PageBreaker extends AbstractBreaker { /** {@inheritDoc} */ protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) { + return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null); + } + + /** {@inheritDoc} */ + protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn, + Position positionAtIPDChange, LayoutManager restartLM, List firstElements) { if (!firstPart) { // if this is the first page that will be created by // the current BlockSequence, it could have a break @@ -133,19 +146,13 @@ public class PageBreaker extends AbstractBreaker { pageBreakHandled = true; pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(), pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex()); - return super.getNextBlockList(childLC, nextSequenceStartsOn); + return super.getNextBlockList(childLC, nextSequenceStartsOn, positionAtIPDChange, + restartLM, firstElements); } - /** {@inheritDoc} */ - protected List getNextKnuthElements(LayoutContext context, int alignment) { - List contentList = null; - - while (!childFLM.isFinished() && contentList == null) { - contentList = childFLM.getNextKnuthElements(context, alignment); - } + private boolean containsFootnotes(List contentList, LayoutContext context) { - // scan contentList, searching for footnotes - boolean bFootnotesPresent = false; + boolean containsFootnotes = false; if (contentList != null) { ListIterator contentListIterator = contentList.listIterator(); while (contentListIterator.hasNext()) { @@ -153,7 +160,7 @@ public class PageBreaker extends AbstractBreaker { if (element instanceof KnuthBlockBox && ((KnuthBlockBox) element).hasAnchors()) { // element represents a line with footnote citations - bFootnotesPresent = true; + containsFootnotes = true; LayoutContext footnoteContext = new LayoutContext(context); footnoteContext.setStackLimitBP(context.getStackLimitBP()); footnoteContext.setRefIPD(pslm.getCurrentPV() @@ -173,31 +180,64 @@ public class PageBreaker extends AbstractBreaker { } } } + return containsFootnotes; + } + + private void handleFootnoteSeparator() { + StaticContent footnoteSeparator; + footnoteSeparator = pslm.getPageSequence().getStaticContent("xsl-footnote-separator"); + if (footnoteSeparator != null) { + // the footnote separator can contain page-dependent content such as + // page numbers or retrieve markers, so its areas cannot simply be + // obtained now and repeated in each page; + // we need to know in advance the separator bpd: the actual separator + // could be different from page to page, but its bpd would likely be + // always the same + + // create a Block area that will contain the separator areas + separatorArea = new Block(); + separatorArea.setIPD(pslm.getCurrentPV() + .getRegionReference(Constants.FO_REGION_BODY).getIPD()); + // create a StaticContentLM for the footnote separator + footnoteSeparatorLM + = pslm.getLayoutManagerMaker().makeStaticContentLayoutManager( + pslm, footnoteSeparator, separatorArea); + footnoteSeparatorLM.doLayout(); + + footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD()); + } + } + + /** {@inheritDoc} */ + protected List getNextKnuthElements(LayoutContext context, int alignment) { + List contentList = null; + + while (!childFLM.isFinished() && contentList == null) { + contentList = childFLM.getNextKnuthElements(context, alignment); + } - if (bFootnotesPresent) { + // scan contentList, searching for footnotes + if (containsFootnotes(contentList, context)) { // handle the footnote separator - StaticContent footnoteSeparator; - footnoteSeparator = pslm.getPageSequence().getStaticContent("xsl-footnote-separator"); - if (footnoteSeparator != null) { - // the footnote separator can contain page-dependent content such as - // page numbers or retrieve markers, so its areas cannot simply be - // obtained now and repeated in each page; - // we need to know in advance the separator bpd: the actual separator - // could be different from page to page, but its bpd would likely be - // always the same - - // create a Block area that will contain the separator areas - separatorArea = new Block(); - separatorArea.setIPD(pslm.getCurrentPV() - .getRegionReference(Constants.FO_REGION_BODY).getIPD()); - // create a StaticContentLM for the footnote separator - footnoteSeparatorLM = (StaticContentLayoutManager) - pslm.getLayoutManagerMaker().makeStaticContentLayoutManager( - pslm, footnoteSeparator, separatorArea); - footnoteSeparatorLM.doLayout(); + handleFootnoteSeparator(); + } + return contentList; + } - footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD()); - } + /** {@inheritDoc} */ + protected List getNextKnuthElements(LayoutContext context, int alignment, + Position positionAtIPDChange, LayoutManager restartAtLM) { + List contentList = null; + + do { + contentList = childFLM.getNextKnuthElements(context, alignment, positionAtIPDChange, + restartAtLM); + } while (!childFLM.isFinished() && contentList == null); + + // scan contentList, searching for footnotes + if (containsFootnotes(contentList, context)) { + // handle the footnote separator + handleFootnoteSeparator(); } return contentList; } @@ -241,49 +281,61 @@ public class PageBreaker extends AbstractBreaker { } /** - * Performs phase 3 operation - * - * @param alg page breaking algorithm - * @param partCount part count - * @param originalList the block sequence original list - * @param effectiveList the block sequence effective list + * {@inheritDoc} + * This implementation checks whether to trigger column-balancing, + * or whether to take into account a 'last-page' condition. */ protected void doPhase3(PageBreakingAlgorithm alg, int partCount, BlockSequence originalList, BlockSequence effectiveList) { + if (needColumnBalancing) { - doPhase3WithColumnBalancing(alg, partCount, originalList, effectiveList); - } else { - if (!hasMoreContent() && pslm.getPageSequence().hasPagePositionLast()) { - //last part is reached and we have a "last page" condition - doPhase3WithLastPage(alg, partCount, originalList, effectiveList); - } else { - //Directly add areas after finding the breaks - addAreas(alg, partCount, originalList, effectiveList); + //column balancing for the last part + doPhase3(alg, partCount, originalList, effectiveList, false); + return; + } + + boolean lastPageMasterDefined = pslm.getPageSequence().hasPagePositionLast(); + if (!hasMoreContent()) { + //last part is reached + if (lastPageMasterDefined) { + //last-page condition + doPhase3(alg, partCount, originalList, effectiveList, true); + return; } } + + //nothing special: just add the areas now + addAreas(alg, partCount, originalList, effectiveList); } - private void doPhase3WithLastPage(PageBreakingAlgorithm alg, int partCount, - BlockSequence originalList, BlockSequence effectiveList) { - int newStartPos; + /** + * Restart the algorithm at the break corresponding + * to the given partCount + * (currently only used to redo the part after the + * last break in case of column-balancing + * and/or a last page-master) + */ + private void doPhase3(PageBreakingAlgorithm alg, int partCount, + BlockSequence originalList, BlockSequence effectiveList, + boolean isLastPart) { + + + int newStartPos = 0; int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount); if (restartPoint > 0) { - //Add definitive areas before last page + //Add definitive areas for the parts before the + //restarting point addAreas(alg, restartPoint, originalList, effectiveList); //Get page break from which we restart PageBreakPosition pbp = (PageBreakPosition) alg.getPageBreaks().get(restartPoint - 1); - //Set starting position to the first element *after* the page-break newStartPos = pbp.getLeafPos() + 1; //Handle page break right here to avoid any side-effects if (newStartPos > 0) { handleBreakTrait(Constants.EN_PAGE); } - } else { - newStartPos = 0; } - AbstractBreaker.log.debug("Last page handling now!!!"); - AbstractBreaker.log.debug("==================================================="); + AbstractBreaker.log.debug("Restarting at " + restartPoint + ", new start position: " + newStartPos); @@ -292,95 +344,78 @@ public class PageBreaker extends AbstractBreaker { int currentPageNum = pslm.getCurrentPageNum(); pageProvider.setStartOfNextElementList(currentPageNum, pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex()); - pageProvider.setLastPageIndex(currentPageNum); - - //Restart last page - PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm( - getTopLevelLM(), - getPageProvider(), createLayoutListener(), - alg.getAlignment(), alg.getAlignmentLast(), - footnoteSeparatorLength, - isPartOverflowRecoveryActivated(), false, false); - //alg.setConstantLineWidth(flowBPD); - int iOptPageCount = algRestart.findBreakingPoints(effectiveList, - newStartPos, - 1, true, BreakingAlgorithm.ALL_BREAKS); - AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount - + " pageBreaks.size()= " + algRestart.getPageBreaks().size()); + + PageBreakingAlgorithm algRestart = null; + int optimalPageCount; //Make sure we only add the areas we haven't added already effectiveList.ignoreAtStart = newStartPos; - boolean replaceLastPage - = iOptPageCount <= pslm.getCurrentPV().getBodyRegion().getColumnCount(); - if (replaceLastPage) { - //Replace last page - pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum)); - addAreas(algRestart, iOptPageCount, originalList, effectiveList); - } else { - addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList); - //Add blank last page - pageProvider.setLastPageIndex(currentPageNum + 1); - pslm.setCurrentPage(pslm.makeNewPage(true, true)); - } - AbstractBreaker.log.debug("==================================================="); - } - private void doPhase3WithColumnBalancing(PageBreakingAlgorithm alg, int partCount, - BlockSequence originalList, BlockSequence effectiveList) { - AbstractBreaker.log.debug("Column balancing now!!!"); - AbstractBreaker.log.debug("==================================================="); - int newStartPos; - int restartPoint = pageProvider.getStartingPartIndexForLastPage(partCount); - if (restartPoint > 0) { - //Add definitive areas - addAreas(alg, restartPoint, originalList, effectiveList); - //Get page break from which we restart - PageBreakPosition pbp = (PageBreakPosition) - alg.getPageBreaks().get(restartPoint - 1); - newStartPos = pbp.getLeafPos(); - //Handle page break right here to avoid any side-effects - if (newStartPos > 0) { - handleBreakTrait(Constants.EN_PAGE); - } - } else { - newStartPos = 0; + if (isLastPart) { + pageProvider.setLastPageIndex(currentPageNum); } - AbstractBreaker.log.debug("Restarting at " + restartPoint - + ", new start position: " + newStartPos); - pageBreakHandled = true; - //Update so the available BPD is reported correctly - pageProvider.setStartOfNextElementList(pslm.getCurrentPageNum(), - pslm.getCurrentPV().getCurrentSpan().getCurrentFlowIndex()); + if (needColumnBalancing) { + AbstractBreaker.log.debug("Column balancing now!!!"); + AbstractBreaker.log.debug("==================================================="); + + //Restart last page + algRestart = new BalancingColumnBreakingAlgorithm( + getTopLevelLM(), getPageProvider(), createLayoutListener(), + alignment, Constants.EN_START, footnoteSeparatorLength, + isPartOverflowRecoveryActivated(), + pslm.getCurrentPV().getBodyRegion().getColumnCount()); + AbstractBreaker.log.debug("==================================================="); + } else { + //plain last page, no column balancing + AbstractBreaker.log.debug("Last page handling now!!!"); + AbstractBreaker.log.debug("==================================================="); + //Restart last page + algRestart = new PageBreakingAlgorithm( + getTopLevelLM(), getPageProvider(), createLayoutListener(), + alg.getAlignment(), alg.getAlignmentLast(), + footnoteSeparatorLength, + isPartOverflowRecoveryActivated(), false, false); + AbstractBreaker.log.debug("==================================================="); + } - //Restart last page - PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm( - getTopLevelLM(), - getPageProvider(), createLayoutListener(), - alignment, Constants.EN_START, footnoteSeparatorLength, - isPartOverflowRecoveryActivated(), - pslm.getCurrentPV().getBodyRegion().getColumnCount()); - //alg.setConstantLineWidth(flowBPD); - int iOptPageCount = algRestart.findBreakingPoints(effectiveList, + optimalPageCount = algRestart.findBreakingPoints(effectiveList, newStartPos, 1, true, BreakingAlgorithm.ALL_BREAKS); - AbstractBreaker.log.debug("restart: iOptPageCount= " + iOptPageCount + AbstractBreaker.log.debug("restart: optimalPageCount= " + optimalPageCount + " pageBreaks.size()= " + algRestart.getPageBreaks().size()); - if (iOptPageCount > pslm.getCurrentPV().getBodyRegion().getColumnCount()) { - AbstractBreaker.log.warn( - "Breaking algorithm produced more columns than are available."); - /* reenable when everything works - throw new IllegalStateException( - "Breaking algorithm must not produce more columns than available."); - */ + + boolean fitsOnePage + = optimalPageCount <= pslm.getCurrentPV().getBodyRegion().getColumnCount(); + + if (isLastPart) { + if (fitsOnePage) { + //Replace last page + pslm.setCurrentPage(pageProvider.getPage(false, currentPageNum)); + } else { + //Last page-master cannot hold the content. + //Add areas now... + addAreas(alg, restartPoint, partCount - restartPoint, originalList, effectiveList); + //...and add a blank last page + pageProvider.setLastPageIndex(currentPageNum + 1); + pslm.setCurrentPage(pslm.makeNewPage(true, true)); + return; + } + } else { + if (!fitsOnePage) { + AbstractBreaker.log.warn( + "Breaking algorithm produced more columns than are available."); + /* reenable when everything works + throw new IllegalStateException( + "Breaking algorithm must not produce more columns than available."); + */ + } } - //Make sure we only add the areas we haven't added already - effectiveList.ignoreAtStart = newStartPos; - addAreas(algRestart, iOptPageCount, originalList, effectiveList); - AbstractBreaker.log.debug("==================================================="); + + addAreas(algRestart, optimalPageCount, originalList, effectiveList); } protected void startPart(BlockSequence list, int breakClass) { - AbstractBreaker.log.debug("startPart() breakClass=" + breakClass); + AbstractBreaker.log.debug("startPart() breakClass=" + getBreakClassName(breakClass)); if (pslm.getCurrentPage() == null) { throw new IllegalStateException("curPage must not be null"); } @@ -416,7 +451,7 @@ public class PageBreaker extends AbstractBreaker { || pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) { // call addAreas() for each FootnoteBodyLM for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) { - LinkedList elementList = alg.getFootnoteList(i); + List elementList = alg.getFootnoteList(i); int firstIndex = (i == pbp.footnoteFirstListIndex ? pbp.footnoteFirstElementIndex : 0); int lastIndex = (i == pbp.footnoteLastListIndex @@ -441,9 +476,7 @@ public class PageBreaker extends AbstractBreaker { pslm.getCurrentPV().getCurrentSpan().notifyFlowsFinished(); } - /** - * @return the current child flow layout manager - */ + /** @return the current child flow layout manager */ protected LayoutManager getCurrentChildLM() { return childFLM; } @@ -462,45 +495,51 @@ public class PageBreaker extends AbstractBreaker { */ private void handleBreakTrait(int breakVal) { Page curPage = pslm.getCurrentPage(); - if (breakVal == Constants.EN_ALL) { + switch (breakVal) { + case Constants.EN_ALL: //break due to span change in multi-column layout curPage.getPageViewport().createSpan(true); return; - } else if (breakVal == Constants.EN_NONE) { + case Constants.EN_NONE: curPage.getPageViewport().createSpan(false); return; - } else if (breakVal == Constants.EN_COLUMN - || breakVal <= 0 - || breakVal == Constants.EN_AUTO) { + case Constants.EN_COLUMN: + case Constants.EN_AUTO: + case Constants.EN_PAGE: + case -1: PageViewport pv = curPage.getPageViewport(); //Check if previous page was spanned boolean forceNewPageWithSpan = false; RegionBody rb = (RegionBody)curPage.getSimplePageMaster().getRegion( Constants.FO_REGION_BODY); - if (breakVal < 0 - && rb.getColumnCount() > 1 - && pv.getCurrentSpan().getColumnCount() == 1) { - forceNewPageWithSpan = true; - } + forceNewPageWithSpan + = (rb.getColumnCount() > 1 + && pv.getCurrentSpan().getColumnCount() == 1); if (forceNewPageWithSpan) { + log.trace("Forcing new page with span"); curPage = pslm.makeNewPage(false, false); curPage.getPageViewport().createSpan(true); } else if (pv.getCurrentSpan().hasMoreFlows()) { + log.trace("Moving to next flow"); pv.getCurrentSpan().moveToNextFlow(); } else { - curPage = pslm.makeNewPage(false, false); + log.trace("Making new page"); + /*curPage = */pslm.makeNewPage(false, false); } return; - } - log.debug("handling break-before after page " + pslm.getCurrentPageNum() - + " breakVal=" + breakVal); - if (needBlankPageBeforeNew(breakVal)) { - curPage = pslm.makeNewPage(true, false); - } - if (needNewPage(breakVal)) { - curPage = pslm.makeNewPage(false, false); + default: + log.debug("handling break-before after page " + pslm.getCurrentPageNum() + + " breakVal=" + getBreakClassName(breakVal)); + if (needBlankPageBeforeNew(breakVal)) { + log.trace("Inserting blank page"); + /*curPage = */pslm.makeNewPage(true, false); + } + if (needNewPage(breakVal)) { + log.trace("Making new page"); + /*curPage = */pslm.makeNewPage(false, false); + } } } @@ -540,4 +579,4 @@ public class PageBreaker extends AbstractBreaker { return true; } } -}
\ No newline at end of file +} diff --git a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java index 8095feba1..52238e9be 100644 --- a/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java +++ b/src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java @@ -20,16 +20,19 @@ package org.apache.fop.layoutmgr; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition; import org.apache.fop.traits.MinOptMax; +import org.apache.fop.util.ListUtil; class PageBreakingAlgorithm extends BreakingAlgorithm { @@ -47,9 +50,9 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { * List<List<KnuthElement>>, it contains the sequences of KnuthElement * representing the footnotes bodies. */ - private ArrayList footnotesList = null; + private List footnotesList = null; /** Cumulated bpd of unhandled footnotes. */ - private ArrayList lengthList = null; + private List lengthList = null; /** Length of all the footnotes which will be put on the current page. */ private int totalFootnotesLength = 0; /** @@ -58,6 +61,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { * footnotes from its preceding pages. */ private int insertedFootnotesLength = 0; + /** True if footnote citations have been met since the beginning of the page sequence. */ private boolean footnotesPending = false; /** @@ -92,6 +96,14 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { //Controls whether a single part should be forced if possible (ex. block-container) private boolean favorSinglePart = false; + private boolean ipdChange; + private KnuthNode bestNodeForIPDChange; + + //Used to keep track of switches in keep-context + private int currentKeepContext = Constants.EN_AUTO; + private KnuthNode lastBeforeKeepContextSwitch; + + public PageBreakingAlgorithm(LayoutManager topLevelLM, PageProvider pageProvider, PageBreakingLayoutListener layoutListener, @@ -178,6 +190,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } + /** {@inheritDoc} */ protected void initialize() { super.initialize(); insertedFootnotesLength = 0; @@ -185,6 +198,73 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { footnoteElementIndex = -1; } + /** + * {@inheritDoc} + * Overridden to defer a part to the next page, if it + * must be kept within one page, but is too large to fit in + * the last column. + */ + protected KnuthNode recoverFromTooLong(KnuthNode lastTooLong) { + + if (log.isDebugEnabled()) { + log.debug("Recovering from too long: " + lastTooLong); + log.debug("\tlastTooShort = " + getLastTooShort()); + log.debug("\tlastBeforeKeepContextSwitch = " + lastBeforeKeepContextSwitch); + log.debug("\tcurrentKeepContext = " + AbstractBreaker.getBreakClassName(currentKeepContext)); + } + + if (lastBeforeKeepContextSwitch == null + || currentKeepContext == Constants.EN_AUTO) { + return super.recoverFromTooLong(lastTooLong); + } + + KnuthNode node = lastBeforeKeepContextSwitch; + lastBeforeKeepContextSwitch = null; + // content would overflow, insert empty page/column(s) and try again + while (!pageProvider.endPage(node.line - 1)) { + log.trace("Adding node for empty column"); + node = createNode( + node.position, + node.line + 1, 1, + 0, 0, 0, + 0, 0, 0, + 0, 0, node); + } + return node; + } + + /** + * Compare two KnuthNodes and return the node with the least demerit. + * + * @param node1 The first knuth node. + * @param node2 The other knuth node. + * @return the node with the least demerit. + */ + protected KnuthNode compareNodes(KnuthNode node1, KnuthNode node2) { + + /* if either node is null, return the other one */ + if (node1 == null || node2 == null) { + return (node1 == null) ? node2 : node1; + } + + /* if either one of the nodes corresponds to a mere column-break, + * and the other one corresponds to a page-break, return the page-break node + */ + if (pageProvider != null) { + if (pageProvider.endPage(node1.line - 1) + && !pageProvider.endPage(node2.line - 1)) { + return node1; + } else if (pageProvider.endPage(node2.line - 1) + && !pageProvider.endPage(node1.line - 1)) { + return node2; + } + } + + /* all other cases: use superclass implementation */ + return super.compareNodes(node1, node2); + } + + /** {@inheritDoc} */ protected KnuthNode createNode(int position, int line, int fitness, int totalWidth, int totalStretch, int totalShrink, double adjustRatio, int availableShrink, int availableStretch, @@ -196,6 +276,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { difference, totalDemerits, previous); } + /** {@inheritDoc} */ protected KnuthNode createNode(int position, int line, int fitness, int totalWidth, int totalStretch, int totalShrink) { return new KnuthPageNode(position, line, fitness, @@ -209,11 +290,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } /** + * {@inheritDoc} * Page-breaking specific handling of the given box. Currently it adds the footnotes * cited in the given box to the list of to-be-handled footnotes. * @param box a block-level element possibly containing foonotes citations */ protected void handleBox(KnuthBox box) { + super.handleBox(box); if (box instanceof KnuthBlockBox && ((KnuthBlockBox) box).hasAnchors()) { handleFootnotes(((KnuthBlockBox) box).getElementLists()); @@ -225,6 +308,28 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } /** + * {@inheritDoc} + * Overridden to consider penalties with value {@link KnuthElement#INFINITE} + * as legal break-points, if the current keep-context allows this + * (a keep-*.within-page="always" constraint still permits column-breaks) + */ + protected void handlePenaltyAt(KnuthPenalty penalty, int position, + int allowedBreaks) { + super.handlePenaltyAt(penalty, position, allowedBreaks); + /* if the penalty had value INFINITE, default implementation + * will not have considered it a legal break, but it could still + * be one. + */ + if (penalty.getP() == KnuthPenalty.INFINITE) { + int breakClass = penalty.getBreakClass(); + if (breakClass == Constants.EN_PAGE + || breakClass == Constants.EN_COLUMN) { + considerLegalBreak(penalty, position); + } + } + } + + /** * Handles the footnotes cited inside a block-level box. Updates footnotesList and the * value of totalFootnotesLength with the lengths of the given footnotes. * @param elementLists list of KnuthElement sequences corresponding to the footnotes @@ -244,9 +349,9 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } // compute the total length of the footnotes - ListIterator elementListsIterator = elementLists.listIterator(); - while (elementListsIterator.hasNext()) { - LinkedList noteList = (LinkedList) elementListsIterator.next(); + for (Iterator elementListsIterator = elementLists.iterator(); + elementListsIterator.hasNext();) { + final List noteList = (List) elementListsIterator.next(); //Space resolution (Note: this does not respect possible stacking constraints //between footnotes!) @@ -254,21 +359,23 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { int noteLength = 0; footnotesList.add(noteList); - ListIterator noteListIterator = noteList.listIterator(); - while (noteListIterator.hasNext()) { - KnuthElement element = (KnuthElement) noteListIterator.next(); + for (Iterator noteListIterator = noteList.iterator(); + noteListIterator.hasNext();) { + final KnuthElement element = (KnuthElement) noteListIterator.next(); if (element.isBox() || element.isGlue()) { noteLength += element.getW(); } } - int prevLength = (lengthList.size() == 0 + int prevLength = (lengthList == null || lengthList.isEmpty()) ? 0 - : ((Integer) lengthList.get(lengthList.size() - 1)).intValue()); + : ((Integer) ListUtil.getLast(lengthList)).intValue(); + //TODO: replace with Integer.valueOf() once we switch to Java 5 lengthList.add(new Integer(prevLength + noteLength)); totalFootnotesLength += noteLength; } } + /** {@inheritDoc} */ protected int restartFrom(KnuthNode restartingNode, int currentIndex) { int returnValue = super.restartFrom(restartingNode, currentIndex); newFootnotes = false; @@ -276,10 +383,10 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // remove from footnotesList the note lists that will be met // after the restarting point for (int j = currentIndex; j >= restartingNode.position; j--) { - KnuthElement resettedElement = getElement(j); - if (resettedElement instanceof KnuthBlockBox - && ((KnuthBlockBox) resettedElement).hasAnchors()) { - resetFootnotes(((KnuthBlockBox) resettedElement).getElementLists()); + final KnuthElement resetElement = getElement(j); + if (resetElement instanceof KnuthBlockBox + && ((KnuthBlockBox) resetElement).hasAnchors()) { + resetFootnotes(((KnuthBlockBox) resetElement).getElementLists()); } } } @@ -288,12 +395,12 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { private void resetFootnotes(List elementLists) { for (int i = 0; i < elementLists.size(); i++) { - /*LinkedList removedList = (LinkedList)*/footnotesList.remove(footnotesList.size() - 1); - lengthList.remove(lengthList.size() - 1); + /*LinkedList removedList = (LinkedList)*/ListUtil.removeLast(footnotesList); + ListUtil.removeLast(lengthList); // update totalFootnotesLength - if (lengthList.size() > 0) { - totalFootnotesLength = ((Integer) lengthList.get(lengthList.size() - 1)).intValue(); + if (!lengthList.isEmpty()) { + totalFootnotesLength = ((Integer) ListUtil.getLast(lengthList)).intValue(); } else { totalFootnotesLength = 0; } @@ -304,16 +411,72 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } + /** {@inheritDoc} */ protected void considerLegalBreak(KnuthElement element, int elementIdx) { + if (element.isPenalty()) { + int breakClass = ((KnuthPenalty) element).getBreakClass(); + switch (breakClass) { + case Constants.EN_PAGE: + if (this.currentKeepContext != breakClass) { + this.lastBeforeKeepContextSwitch = getLastTooShort(); + } + this.currentKeepContext = breakClass; + break; + case Constants.EN_COLUMN: + if (this.currentKeepContext != breakClass) { + this.lastBeforeKeepContextSwitch = getLastTooShort(); + } + this.currentKeepContext = breakClass; + break; + case Constants.EN_AUTO: + this.currentKeepContext = breakClass; + break; + default: + //nop + } + } super.considerLegalBreak(element, elementIdx); newFootnotes = false; } + /** {@inheritDoc} */ + protected boolean elementCanEndLine(KnuthElement element, int line, int difference) { + if (!(element.isPenalty()) || pageProvider == null) { + return true; + } else { + KnuthPenalty p = (KnuthPenalty) element; + if (p.getP() <= 0) { + return true; + } else { + int context = p.getBreakClass(); + switch (context) { + case Constants.EN_LINE: + case Constants.EN_COLUMN: + return p.getP() < KnuthPenalty.INFINITE; + case Constants.EN_PAGE: + return p.getP() < KnuthPenalty.INFINITE + || !pageProvider.endPage(line - 1); + case Constants.EN_AUTO: + log.debug("keep is not auto but context is"); + return true; + default: + if (p.getP() < KnuthPenalty.INFINITE) { + log.debug("Non recognized keep context:" + context); + return true; + } else { + return false; + } + } + } + } + } + + /** {@inheritDoc} */ protected int computeDifference(KnuthNode activeNode, KnuthElement element, int elementIndex) { KnuthPageNode pageNode = (KnuthPageNode) activeNode; int actualWidth = totalWidth - pageNode.totalWidth; - int footnoteSplit; + int footnoteSplit = 0; boolean canDeferOldFootnotes; if (element.isPenalty()) { actualWidth += element.getW(); @@ -332,7 +495,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; footnoteElementIndex - = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; + = getFootnoteList(footnoteListIndex).size() - 1; } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex)) || newFootnotes) @@ -358,7 +521,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; footnoteListIndex = footnotesList.size() - 1; footnoteElementIndex - = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; + = getFootnoteList(footnoteListIndex).size() - 1; } } else { // all footnotes have already been placed on previous pages @@ -375,7 +538,8 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } - /** Checks whether footnotes from preceding pages may be deferred to the page after + /** + * Checks whether footnotes from preceding pages may be deferred to the page after * the given element. * @param node active node for the preceding page break * @param contentElementIndex index of the Knuth element considered for the @@ -448,7 +612,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { return ((newFootnotes && firstNewFootnoteIndex != 0 && (listIndex < firstNewFootnoteIndex - 1 - || elementIndex < ((LinkedList) footnotesList.get(listIndex)).size() - 1)) + || elementIndex < getFootnoteList(listIndex).size() - 1)) || length < totalFootnotesLength); } @@ -457,6 +621,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { * @param activeNode currently considered previous page break * @param availableLength available space for footnotes * @param canDeferOldFootnotes + * @return ... */ private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) { @@ -473,6 +638,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { * @param prevLength total length of footnotes inserted so far * @param availableLength available space for footnotes on this page * @param canDeferOldFootnotes + * @return ... */ private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength, int availableLength, boolean canDeferOldFootnotes) { @@ -491,7 +657,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // already placed in a page: advance to the next element int listIndex = prevListIndex; int elementIndex = prevElementIndex; - if (elementIndex == ((LinkedList) footnotesList.get(listIndex)).size() - 1) { + if (elementIndex == getFootnoteList(listIndex).size() - 1) { listIndex++; elementIndex = 0; } else { @@ -501,9 +667,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // try adding whole notes if (footnotesList.size() - 1 > listIndex) { // add the previous footnotes: these cannot be broken or deferred - if (!canDeferOldFootnotes - && newFootnotes - && firstNewFootnoteIndex > 0) { + if (!canDeferOldFootnotes && newFootnotes && firstNewFootnoteIndex > 0) { splitLength = ((Integer) lengthList.get(firstNewFootnoteIndex - 1)).intValue() - prevLength; listIndex = firstNewFootnoteIndex; @@ -524,8 +688,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } // try adding a split of the next note - noteListIterator = ((LinkedList) footnotesList.get(listIndex)) - .listIterator(elementIndex); + noteListIterator = getFootnoteList(listIndex).listIterator(elementIndex); int prevSplitLength = 0; int prevIndex = -1; @@ -539,7 +702,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { prevIndex = index; } // get a sub-sequence from the note element list - boolean bPrevIsBox = false; + boolean boxPreceding = false; while (noteListIterator.hasNext()) { // as this method is called only if it is not possible to insert // all footnotes, and we have already tried (and failed) to insert @@ -549,15 +712,15 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { if (element.isBox()) { // element is a box splitLength += element.getW(); - bPrevIsBox = true; + boxPreceding = true; } else if (element.isGlue()) { // element is a glue - if (bPrevIsBox) { + if (boxPreceding) { // end of the sub-sequence index = noteListIterator.previousIndex(); break; } - bPrevIsBox = false; + boxPreceding = false; splitLength += element.getW(); } else { // element is a penalty @@ -569,11 +732,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } } + // if prevSplitLength is 0, this means that the available length isn't enough // to insert even the smallest split of the last footnote, so we cannot end a // page here // if prevSplitLength is > 0 we can insert some footnote content in this page // and insert the remaining in the following one + //TODO: check this conditional, as the first one is always false...? if (!somethingAdded) { // there was not enough space to add a piece of the first new footnote // this is not a good break @@ -583,12 +748,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { footnoteListIndex = (prevIndex != -1) ? listIndex : listIndex - 1; footnoteElementIndex = (prevIndex != -1) ? prevIndex - : ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1; + : getFootnoteList(footnoteListIndex).size() - 1; } return prevSplitLength; } } + /** {@inheritDoc} */ protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) { // compute the adjustment ratio if (difference > 0) { @@ -618,18 +784,23 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } + /** {@inheritDoc} */ protected double computeDemerits(KnuthNode activeNode, KnuthElement element, int fitnessClass, double r) { double demerits = 0; // compute demerits double f = Math.abs(r); f = 1 + 100 * f * f * f; - if (element.isPenalty() && element.getP() >= 0) { - f += element.getP(); - demerits = f * f; - } else if (element.isPenalty() && !element.isForcedBreak()) { + if (element.isPenalty()) { double penalty = element.getP(); - demerits = f * f - penalty * penalty; + if (penalty >= 0) { + f += penalty; + demerits = f * f; + } else if (!element.isForcedBreak()) { + demerits = f * f - penalty * penalty; + } else { + demerits = f * f; + } } else { demerits = f * f; } @@ -654,7 +825,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } if (footnoteListIndex < footnotesList.size()) { if (footnoteElementIndex - < ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1) { + < getFootnoteList(footnoteListIndex).size() - 1) { // add demerits for the footnote split between pages demerits += splitFootnoteDemerits; } @@ -680,6 +851,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } private void createFootnotePages(KnuthPageNode lastNode) { + insertedFootnotesLength = lastNode.totalFootnotes; footnoteListIndex = lastNode.footnoteListIndex; footnoteElementIndex = lastNode.footnoteElementIndex; @@ -689,18 +861,16 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { // create pages containing the remaining footnote bodies while (insertedFootnotesLength < totalFootnotesLength) { + final int tmpLength = ((Integer) lengthList.get(footnoteListIndex)).intValue(); // try adding some more content - if (((Integer) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength - <= availableBPD) { + if ((tmpLength - insertedFootnotesLength) <= availableBPD) { // add a whole footnote - availableBPD -= ((Integer) lengthList.get(footnoteListIndex)).intValue() - - insertedFootnotesLength; - insertedFootnotesLength = ((Integer)lengthList.get(footnoteListIndex)).intValue(); + availableBPD -= tmpLength - insertedFootnotesLength; + insertedFootnotesLength = tmpLength; footnoteElementIndex - = ((LinkedList)footnotesList.get(footnoteListIndex)).size() - 1; + = getFootnoteList(footnoteListIndex).size() - 1; } else if ((split = getFootnoteSplit(footnoteListIndex, footnoteElementIndex, - insertedFootnotesLength, availableBPD, true)) - > 0) { + insertedFootnotesLength, availableBPD, true)) > 0) { // add a piece of a footnote availableBPD -= split; insertedFootnotesLength += split; @@ -732,12 +902,19 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } /** - * @return a list of PageBreakPosition elements + * @return a list of {@link PageBreakPosition} elements + * corresponding to the computed page- and column-breaks */ public LinkedList getPageBreaks() { return pageBreaks; } + /** + * Insert the given {@link PageBreakPosition} as the first + * element in the list of page-breaks + * + * @param pageBreak the position to insert + */ public void insertPageBreakAsFirst(PageBreakPosition pageBreak) { if (pageBreaks == null) { pageBreaks = new LinkedList(); @@ -759,9 +936,11 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } + /** {@inheritDoc} */ public void updateData1(int total, double demerits) { } + /** {@inheritDoc} */ public void updateData2(KnuthNode bestActiveNode, KnuthSequence sequence, int total) { @@ -807,7 +986,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex; int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex; if (footnotesList != null - && firstElementIndex == ((LinkedList) footnotesList.get(firstListIndex)).size() - 1) { + && firstElementIndex == getFootnoteList(firstListIndex).size() - 1) { // advance to the next list firstListIndex++; firstElementIndex = 0; @@ -829,6 +1008,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { ratio, difference)); } + /** {@inheritDoc} */ protected int filterActiveNodes() { // leave only the active node with fewest total demerits KnuthNode bestActiveNode = null; @@ -848,11 +1028,18 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } } } + assert (bestActiveNode != null); return bestActiveNode.line; } - public LinkedList getFootnoteList(int index) { - return (LinkedList) footnotesList.get(index); + /** + * Obtain the element-list corresponding to the footnote at the given index. + * + * @param index the index in the list of footnotes + * @return the element-list + */ + protected final List getFootnoteList(int index) { + return (List) footnotesList.get(index); } /** @return the associated top-level formatting object. */ @@ -889,4 +1076,63 @@ class PageBreakingAlgorithm extends BreakingAlgorithm { } + /** {@inheritDoc} */ + protected boolean ipdChanged() { + return ipdChange; + } + + /** {@inheritDoc} */ + protected int handleIpdChange() { + log.trace("Best node for ipd change:" + bestNodeForIPDChange); + // TODO finish() + /* + * The third parameter is used to determine if this is the last page, so + * if the content must be vertically justified or not. If we are here + * this means that there is further content and the next page has a + * different ipd. So tweak the parameter to fall into the non-last-page + * case. + */ + calculateBreakPoints(bestNodeForIPDChange, par, bestNodeForIPDChange.line + 1); + activeLines = null; + return bestNodeForIPDChange.line; + } + + /** + * Add a node at the end of the given line's existing active nodes. + * If this is the first node in the line, adjust endLine accordingly. + * @param line number of the line ending at the node's corresponding breakpoint + * @param node the active node to add + */ + protected void addNode(int line, KnuthNode node) { + if (node.position < par.size() - 1 && line > 0 && ipdChange(line - 1)) { + log.trace("IPD changes at page " + line); + ipdChange = true; + if (bestNodeForIPDChange == null + || node.totalDemerits < bestNodeForIPDChange.totalDemerits) { + bestNodeForIPDChange = node; + } + } else { + if (node.position == par.size() - 1) { + /* + * The whole sequence could actually fit on the last page before + * the IPD change. No need to do any special handling. + */ + ipdChange = false; + } + super.addNode(line, node); + } + } + + KnuthNode getBestNodeBeforeIPDChange() { + return bestNodeForIPDChange; + } + + /** {@inheritDoc} */ + protected boolean ipdChange(int line) { + if (pageProvider == null) { + return false; + } + return pageProvider.ipdChange(line); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/PageProvider.java b/src/java/org/apache/fop/layoutmgr/PageProvider.java index 9073c48bb..bd556366a 100644 --- a/src/java/org/apache/fop/layoutmgr/PageProvider.java +++ b/src/java/org/apache/fop/layoutmgr/PageProvider.java @@ -146,6 +146,76 @@ public class PageProvider implements Constants { return this.lastReportedBPD; } + // Wish there were a more elegant way to do this in Java + private int[] getColIndexAndColCount(int index) { + int columnCount = 0; + int colIndex = startColumnOfCurrentElementList + index; + int pageIndex = -1; + do { + colIndex -= columnCount; + pageIndex++; + Page page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); + columnCount = page.getPageViewport().getCurrentSpan().getColumnCount(); + } while (colIndex >= columnCount); + return new int[] {colIndex, columnCount}; + } + + /** + * Returns true if the part following the given one has a different IPD. + * + * @param index index of the current part + * @return true if the following part has a different IPD, false otherwise + */ + public boolean ipdChange(int index) { + int columnCount = 0; + int colIndex = startColumnOfCurrentElementList + index; + int pageIndex = -1; + Page page; + do { + colIndex -= columnCount; + pageIndex++; + page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST); + columnCount = page.getPageViewport().getCurrentSpan().getColumnCount(); + } while (colIndex >= columnCount); + if (colIndex + 1 < columnCount) { + // Next part is a column on same page => same IPD + return false; + } else { + Page nextPage = getPage(false, pageIndex + 1, RELTO_CURRENT_ELEMENT_LIST); + return page.getPageViewport().getBodyRegion().getIPD() + != nextPage.getPageViewport().getBodyRegion().getIPD(); + } + } + + /** + * Checks if a break at the passed index would start a new page + * @param index the index of the element before the break + * @return {@code true} if the break starts a new page + */ + boolean startPage(int index) { + return getColIndexAndColCount(index)[0] == 0; + } + + /** + * Checks if a break at the passed index would end a page + * @param index the index of the element before the break + * @return {@code true} if the break ends a page + */ + boolean endPage(int index) { + int[] colIndexAndColCount = getColIndexAndColCount(index); + return colIndexAndColCount[0] == colIndexAndColCount[1] - 1; + } + + /** + * Obtain the applicable column-count for the element at the + * passed index + * @param index the index of the element + * @return the number of columns + */ + int getColumnCount(int index) { + return getColIndexAndColCount(index)[1]; + } + /** * Returns the part index (0<x<partCount) which denotes the first part on the last page * generated by the current element list. @@ -272,4 +342,4 @@ public class PageProvider implements Constants { return page; } -}
\ No newline at end of file +} diff --git a/src/java/org/apache/fop/layoutmgr/Position.java b/src/java/org/apache/fop/layoutmgr/Position.java index 42034cab4..10ad5878e 100644 --- a/src/java/org/apache/fop/layoutmgr/Position.java +++ b/src/java/org/apache/fop/layoutmgr/Position.java @@ -28,6 +28,11 @@ public class Position { layoutManager = lm; } + public Position(LayoutManager lm, int index) { + this(lm); + setIndex(index); + } + public LayoutManager getLM() { return layoutManager; } diff --git a/src/java/org/apache/fop/layoutmgr/SpaceResolver.java b/src/java/org/apache/fop/layoutmgr/SpaceResolver.java index 416ee924d..ff464ce6a 100644 --- a/src/java/org/apache/fop/layoutmgr/SpaceResolver.java +++ b/src/java/org/apache/fop/layoutmgr/SpaceResolver.java @@ -19,7 +19,6 @@ package org.apache.fop.layoutmgr; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -565,6 +564,11 @@ public class SpaceResolver { public Position getOriginalBreakPosition() { return this.originalPosition; } + + public Position getPosition() { + return originalPosition; + } + } /** diff --git a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java index b4941d418..d5949f4a2 100644 --- a/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/StaticContentLayoutManager.java @@ -21,10 +21,6 @@ package org.apache.fop.layoutmgr; import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.Block; @@ -35,10 +31,7 @@ import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.fo.pagination.SideRegion; import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener; -import org.apache.fop.layoutmgr.inline.InlineLevelLayoutManager; import org.apache.fop.layoutmgr.inline.TextLayoutManager; -import org.apache.fop.traits.MinOptMax; -import org.apache.fop.util.ListUtil; /** * LayoutManager for an fo:flow object. @@ -48,11 +41,6 @@ import org.apache.fop.util.ListUtil; */ public class StaticContentLayoutManager extends BlockStackingLayoutManager { - /** - * logging instance - */ - private static Log log = LogFactory.getLog(StaticContentLayoutManager.class); - private RegionReference targetRegion; private Block targetBlock; private SideRegion regionFO; @@ -89,96 +77,7 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { /** {@inheritDoc} */ public List getNextKnuthElements(LayoutContext context, int alignment) { - if (true) { - throw new UnsupportedOperationException( - "Shouldn't this method be emptied because it's never called at all?"); - } - //TODO Empty this method?!? - // set layout dimensions - setContentAreaIPD(context.getRefIPD()); - setContentAreaBPD(context.getStackLimitBP().opt); - - //TODO Copied from elsewhere. May be worthwhile to factor out the common parts. - // currently active LM - BlockLevelLayoutManager curLM; - BlockLevelLayoutManager prevLM = null; - MinOptMax stackSize = new MinOptMax(); - List returnedList; - List returnList = new LinkedList(); - - while ((curLM = ((BlockLevelLayoutManager) getChildLM())) != null) { - if (curLM instanceof InlineLevelLayoutManager) { - log.error("inline area not allowed under flow - ignoring"); - curLM.setFinished(true); - continue; - } - - // Set up a LayoutContext - MinOptMax bpd = context.getStackLimitBP(); - - LayoutContext childLC = new LayoutContext(0); - childLC.setStackLimitBP(MinOptMax.subtract(bpd, stackSize)); - childLC.setRefIPD(context.getRefIPD()); - - // get elements from curLM - returnedList = curLM.getNextKnuthElements(childLC, alignment); - //log.debug("FLM.getNextKnuthElements> returnedList.size() = " - // + returnedList.size()); - - // "wrap" the Position inside each element - List tempList = returnedList; - KnuthElement tempElement; - returnedList = new LinkedList(); - ListIterator listIter = tempList.listIterator(); - while (listIter.hasNext()) { - tempElement = (KnuthElement)listIter.next(); - tempElement.setPosition(new NonLeafPosition(this, tempElement.getPosition())); - returnedList.add(tempElement); - } - - if (returnedList.size() == 1 - && ((KnuthElement)returnedList.get(0)).isPenalty() - && ((KnuthPenalty)returnedList.get(0)).getP() == -KnuthElement.INFINITE) { - // a descendant of this flow has break-before - returnList.addAll(returnedList); - return returnList; - } else { - if (!returnList.isEmpty()) { - // there is a block before this one - if (prevLM.mustKeepWithNext() - || curLM.mustKeepWithPrevious()) { - // add an infinite penalty to forbid a break between blocks - returnList.add(new KnuthPenalty(0, - KnuthElement.INFINITE, false, - new Position(this), false)); - } else if (!((KnuthElement) ListUtil.getLast(returnList)) - .isGlue()) { - // add a null penalty to allow a break between blocks - returnList.add(new KnuthPenalty(0, 0, false, new Position(this), false)); - } - } -/*LF*/ if (!returnedList.isEmpty()) { // controllare! - returnList.addAll(returnedList); - final KnuthElement last = (KnuthElement) ListUtil - .getLast(returnedList); - if (last.isPenalty() - && ((KnuthPenalty) last).getP() == -KnuthElement.INFINITE) { - // a descendant of this flow has break-after -/*LF*/ //log.debug("FLM - break after!!"); - return returnList; - } -/*LF*/ } - } - prevLM = curLM; - } - - setFinished(true); - - if (returnList.isEmpty()) { - return null; - } else { - return returnList; - } + throw new IllegalStateException(); } /** @@ -415,18 +314,18 @@ public class StaticContentLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - return KEEP_AUTO; + public Keep getKeepTogether() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; } } diff --git a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java index a5247d652..95f798161 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ContentLayoutManager.java @@ -27,6 +27,7 @@ import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.LineArea; @@ -43,7 +44,6 @@ import org.apache.fop.layoutmgr.PageSequenceLayoutManager; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.SpaceSpecifier; -import org.apache.fop.traits.MinOptMax; /** * Content Layout Manager. @@ -111,8 +111,6 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager LayoutContext childLC = new LayoutContext(LayoutContext.NEW_AREA); childLC.setLeadingSpace(new SpaceSpecifier(false)); childLC.setTrailingSpace(new SpaceSpecifier(false)); - // set stackLimit for lines - childLC.setStackLimitIP(new MinOptMax(ipd)); childLC.setRefIPD(ipd); int lineHeight = 14000; @@ -129,8 +127,7 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager stackSize = 0; - List contentList = - getNextKnuthElements(childLC, Constants.EN_START); + List contentList = getNextKnuthElements(childLC, Constants.EN_START); ListIterator contentIter = contentList.listIterator(); while (contentIter.hasNext()) { KnuthElement element = (KnuthElement) contentIter.next(); @@ -149,8 +146,7 @@ public class ContentLayoutManager extends AbstractBaseLayoutManager lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); lc.setLeadingSpace(new SpaceSpecifier(false)); lc.setTrailingSpace(new SpaceSpecifier(false)); - KnuthPossPosIter contentPosIter = - new KnuthPossPosIter(contentList, 0, contentList.size()); + KnuthPossPosIter contentPosIter = new KnuthPossPosIter(contentList, 0, contentList.size()); curLM.addAreas(contentPosIter, lc); } diff --git a/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java b/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java index bacf43dbc..338a58d9c 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java @@ -172,15 +172,15 @@ public class ImageLayout implements Constants { } this.clip = false; - if (cwidth > ipd || cheight > bpd) { - int overflow = props.getOverflow(); - if (overflow == EN_HIDDEN) { - this.clip = true; - } else if (overflow == EN_ERROR_IF_OVERFLOW) { + int overflow = props.getOverflow(); + if (overflow == EN_HIDDEN) { + this.clip = true; + } else if (overflow == EN_ERROR_IF_OVERFLOW) { + if (cwidth > ipd || cheight > bpd) { //TODO Don't use logging to report error! log.error("Object overflows the viewport: clipping"); - this.clip = true; } + this.clip = true; } int xoffset = computeXOffset(ipd, cwidth); diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java index 0c332281f..6e0c34a82 100755 --- a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java @@ -248,8 +248,6 @@ public class InlineLayoutManager extends InlineStackingLayoutManager { List returnList = new LinkedList(); KnuthSequence lastSequence = null; - SpaceSpecifier leadingSpace = context.getLeadingSpace(); - if (fobj instanceof Title) { alignmentContext = new AlignmentContext(font, lineHeight.getOptimum(this).getLength().getValue(this), @@ -274,14 +272,6 @@ public class InlineLayoutManager extends InlineStackingLayoutManager { if (getSpaceStart() != null) { context.getLeadingSpace().addSpace(new SpaceVal(getSpaceStart(), this)); } - - // Check for "fence" - if (hasLeadingFence(!context.isFirstArea())) { - // Reset leading space sequence for child areas - leadingSpace = new SpaceSpecifier(false); - } - // Reset state variables - clearPrevIPD(); // Clear stored prev content dimensions } StringBuffer trace = new StringBuffer("InlineLM:"); diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java index 963b98b37..65e59554f 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java @@ -19,12 +19,13 @@ package org.apache.fop.layoutmgr.inline; -import java.util.LinkedList; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import java.util.HashMap; +import org.apache.fop.area.Area; +import org.apache.fop.area.inline.Space; import org.apache.fop.fo.FObj; import org.apache.fop.fo.properties.SpaceProperty; import org.apache.fop.layoutmgr.AbstractLayoutManager; @@ -34,8 +35,6 @@ import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.NonLeafPosition; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; -import org.apache.fop.area.Area; -import org.apache.fop.area.inline.Space; import org.apache.fop.traits.MinOptMax; /** @@ -62,12 +61,6 @@ public abstract class InlineStackingLayoutManager extends AbstractLayoutManager } } - - /** - * Size of any start or end borders and padding. - */ - private MinOptMax allocIPD = new MinOptMax(0); - /** * Size of border and padding in BPD (ie, before and after). */ @@ -78,9 +71,6 @@ public abstract class InlineStackingLayoutManager extends AbstractLayoutManager /** The child layout context */ protected LayoutContext childLC; - /** Used to store previous content IPD for each child LM. */ - private HashMap hmPrevIPD = new HashMap(); - /** * Create an inline stacking layout manager. * This is used for fo's that create areas that @@ -149,22 +139,6 @@ public abstract class InlineStackingLayoutManager extends AbstractLayoutManager } /** - * TODO: Explain this method - * @param lm ??? - * @return ??? - */ - protected MinOptMax getPrevIPD(LayoutManager lm) { - return (MinOptMax) hmPrevIPD.get(lm); - } - - /** - * Clear the previous IPD calculation. - */ - protected void clearPrevIPD() { - hmPrevIPD.clear(); - } - - /** * Returns the current area. * @return the current area */ diff --git a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java index 3a0672f4e..7c30ab9bb 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java @@ -20,6 +20,7 @@ package org.apache.fop.layoutmgr.inline; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -36,6 +37,7 @@ import org.apache.fop.datatypes.Numeric; import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.Block; import org.apache.fop.fo.properties.CommonHyphenation; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; @@ -46,7 +48,7 @@ import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.BreakingAlgorithm; import org.apache.fop.layoutmgr.ElementListObserver; import org.apache.fop.layoutmgr.InlineKnuthSequence; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBlockBox; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; @@ -115,8 +117,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager * inline break positions. */ private static class LineBreakPosition extends LeafPosition { - private int iParIndex; // index of the Paragraph this Position refers to - private int iStartIndex; //index of the first element this Position refers to + private int parIndex; // index of the Paragraph this Position refers to + private int startIndex; //index of the first element this Position refers to private int availableShrink; private int availableStretch; private int difference; @@ -129,16 +131,16 @@ public class LineLayoutManager extends InlineStackingLayoutManager private int spaceAfter; private int baseline; - LineBreakPosition(LayoutManager lm, int index, int iStartIndex, int iBreakIndex, + LineBreakPosition(LayoutManager lm, int index, int startIndex, int breakIndex, int shrink, int stretch, int diff, double ipdA, double adjust, int ind, int lh, int lw, int sb, int sa, int bl) { - super(lm, iBreakIndex); + super(lm, breakIndex); availableShrink = shrink; availableStretch = stretch; difference = diff; - iParIndex = index; - this.iStartIndex = iStartIndex; + parIndex = index; + this.startIndex = startIndex; ipdAdjust = ipdA; dAdjust = adjust; startIndent = ind; @@ -166,18 +168,18 @@ public class LineLayoutManager extends InlineStackingLayoutManager private Length lineHeight; private int lead; private int follow; - private AlignmentContext alignmentContext = null; + private AlignmentContext alignmentContext; - private List knuthParagraphs = null; - private int iReturnedLBP = 0; - - // parameters of Knuth's algorithm: - // penalty value for flagged penalties - private int flaggedPenalty = 50; + private List knuthParagraphs; private LineLayoutPossibilities lineLayouts; private List lineLayoutsList; - private int iLineWidth = 0; + private int ipd = 0; + /** + * When layout must be re-started due to a change of IPD, there is no need + * to perform hyphenation on the remaining Knuth sequence once again. + */ + private boolean hyphenationPerformed; /** * this constant is used to create elements when text-align is center: @@ -237,7 +239,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager } else { lineFiller = new MinOptMax(lastLineEndIndent, lastLineEndIndent, - layoutManager.iLineWidth); + layoutManager.ipd); } // add auxiliary elements at the beginning of the paragraph @@ -318,11 +320,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager private int activePossibility; private int addedPositions; private int textIndent; - private int fillerMinWidth; private int lineHeight; private int lead; private int follow; - private int maxDiff; private static final double MAX_DEMERITS = 10e6; public LineBreakingAlgorithm (int pageAlign, @@ -333,22 +333,17 @@ public class LineLayoutManager extends InlineStackingLayoutManager super(textAlign, textAlignLast, first, false, maxFlagCount); pageAlignment = pageAlign; textIndent = indent; - fillerMinWidth = fillerWidth; lineHeight = lh; lead = ld; follow = fl; thisLLM = llm; activePossibility = -1; - maxDiff = fobj.getWidows() >= fobj.getOrphans() - ? fobj.getWidows() - : fobj.getOrphans(); } public void updateData1(int lineCount, double demerits) { lineLayouts.addPossibility(lineCount, demerits); - if (super.log.isTraceEnabled()) { - super.log.trace( - "Layout possibility in " + lineCount + " lines; break at position:"); + if (log.isTraceEnabled()) { + log.trace("Layout possibility in " + lineCount + " lines; break at position:"); } } @@ -362,7 +357,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast; indent += (textAlign == Constants.EN_CENTER) ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0; - indent += (bestActiveNode.line == 1 && bFirst && isFirstInBlock) ? textIndent : 0; + indent += (bestActiveNode.line == 1 && indentFirstPart && isFirstInBlock) ? textIndent : 0; double ratio = (textAlign == Constants.EN_JUSTIFY || difference < 0 && -difference <= bestActiveNode.availableShrink) ? bestActiveNode.adjustRatio : 0; @@ -429,7 +424,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager // true if this line contains only zero-height, auxiliary boxes // and the actual line width is 0; in this case, the line "collapses" // i.e. the line area will have bpd = 0 - boolean bZeroHeightLine = (difference == iLineWidth); + boolean bZeroHeightLine = (difference == ipd); // if line-stacking-strategy is "font-height", the line height // is not affected by its content @@ -485,7 +480,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager firstElementIndex, lastElementIndex, availableShrink, availableStretch, difference, ratio, 0, indent, - 0, iLineWidth, 0, 0, 0); + 0, ipd, 0, 0, 0); } else { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), @@ -493,18 +488,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager availableShrink, availableStretch, difference, ratio, 0, indent, lineLead + lineFollow, - iLineWidth, spaceBefore, spaceAfter, + ipd, spaceBefore, spaceAfter, lineLead); } } - public int findBreakingPoints(Paragraph par, /*int lineWidth,*/ - double threshold, boolean force, - int allowedBreaks) { - return super.findBreakingPoints(par, /*lineWidth,*/ - threshold, force, allowedBreaks); - } - protected int filterActiveNodes() { KnuthNode bestActiveNode = null; @@ -577,13 +565,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager FontInfo fi = fobj.getFOEventHandler().getFontInfo(); FontTriplet[] fontkeys = fobj.getCommonFont().getFontState(fi); Font fs = fi.getFontInstance(fontkeys[0], fobj.getCommonFont().fontSize.getValue(this)); - alignmentContext - = new AlignmentContext(fs, lineHeight.getValue(this), context.getWritingMode()); + alignmentContext = new AlignmentContext(fs, lineHeight.getValue(this), + context.getWritingMode()); context.setAlignmentContext(alignmentContext); - // Get a break from currently active child LM - // Set up constraints for inline level managers - - clearPrevIPD(); + ipd = context.getRefIPD(); //PHASE 1: Create Knuth elements if (knuthParagraphs == null) { @@ -605,34 +590,33 @@ public class LineLayoutManager extends InlineStackingLayoutManager //PHASE 2: Create line breaks return createLineBreaks(context.getBPAlignment(), context); - /* - LineBreakPosition lbp = null; - if (breakpoints == null) { - // find the optimal line breaking points for each paragraph - breakpoints = new ArrayList(); - ListIterator paragraphsIterator - = knuthParagraphs.listIterator(knuthParagraphs.size()); - Paragraph currPar = null; - while (paragraphsIterator.hasPrevious()) { - currPar = (Paragraph) paragraphsIterator.previous(); - findBreakingPoints(currPar, context.getStackLimit().opt); - } - }*/ + } - //PHASE 3: Return lines + public List getNextKnuthElements(LayoutContext context, int alignment, + LeafPosition restartPosition) { + log.trace("Restarting line breaking from index " + restartPosition.getIndex()); + int parIndex = restartPosition.getLeafPos(); + Paragraph paragraph = (Paragraph) knuthParagraphs.get(parIndex); + for (int i = 0; i <= restartPosition.getIndex(); i++) { + paragraph.remove(0); + } + Iterator iter = paragraph.iterator(); + while (iter.hasNext() && !((KnuthElement) iter.next()).isBox()) { + iter.remove(); + } + if (!iter.hasNext()) { + knuthParagraphs.remove(parIndex); + } - /* - // get a break point from the list - lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++); - if (iReturnedLBP == breakpoints.size()) { + // return finished when there's no content + if (knuthParagraphs.size() == 0) { setFinished(true); + return null; } - BreakPoss curLineBP = new BreakPoss(lbp); - curLineBP.setFlag(BreakPoss.ISLAST, isFinished()); - curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight)); - return curLineBP; - */ + ipd = context.getRefIPD(); + //PHASE 2: Create line breaks + return createLineBreaks(context.getBPAlignment(), context); } /** @@ -642,22 +626,18 @@ public class LineLayoutManager extends InlineStackingLayoutManager private void collectInlineKnuthElements(LayoutContext context) { LayoutContext inlineLC = new LayoutContext(context); - InlineLevelLayoutManager curLM; - List returnedList = null; - iLineWidth = context.getStackLimitIP().opt; - // convert all the text in a sequence of paragraphs made // of KnuthBox, KnuthGlue and KnuthPenalty objects - boolean bPrevWasKnuthBox = false; + boolean previousIsBox = false; StringBuffer trace = new StringBuffer("LineLM:"); Paragraph lastPar = null; + InlineLevelLayoutManager curLM; while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) { - returnedList = curLM.getNextKnuthElements(inlineLC, effectiveAlignment); - if (returnedList == null - || returnedList.size() == 0) { + List inlineElements = curLM.getNextKnuthElements(inlineLC, effectiveAlignment); + if (inlineElements == null || inlineElements.size() == 0) { /* curLM.getNextKnuthElements() returned null or an empty list; * this can happen if there is nothing more to layout, * so just iterate once more to see if there are other children */ @@ -665,7 +645,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager } if (lastPar != null) { - KnuthSequence firstSeq = (KnuthSequence) returnedList.get(0); + KnuthSequence firstSeq = (KnuthSequence) inlineElements.get(0); // finish last paragraph before a new block sequence if (!firstSeq.isInlineSequence()) { @@ -675,7 +655,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager if (log.isTraceEnabled()) { trace.append(" ]"); } - bPrevWasKnuthBox = false; + previousIsBox = false; } // does the first element of the first paragraph add to an existing word? @@ -683,27 +663,24 @@ public class LineLayoutManager extends InlineStackingLayoutManager KnuthElement thisElement; thisElement = (KnuthElement) firstSeq.get(0); if (thisElement.isBox() && !thisElement.isAuxiliary() - && bPrevWasKnuthBox) { + && previousIsBox) { lastPar.addALetterSpace(); } } } // loop over the KnuthSequences (and single KnuthElements) in returnedList - ListIterator iter = returnedList.listIterator(); + ListIterator iter = inlineElements.listIterator(); while (iter.hasNext()) { KnuthSequence sequence = (KnuthSequence) iter.next(); // the sequence contains inline Knuth elements if (sequence.isInlineSequence()) { // look at the last element ListElement lastElement = sequence.getLast(); - if (lastElement == null) { - throw new NullPointerException( - "Sequence was empty! lastElement is null"); - } - bPrevWasKnuthBox = lastElement.isBox() - && !((KnuthElement) lastElement).isAuxiliary() - && ((KnuthElement) lastElement).getW() != 0; + assert lastElement != null; + previousIsBox = lastElement.isBox() + && !((KnuthElement) lastElement).isAuxiliary() + && ((KnuthElement) lastElement).getW() != 0; // if last paragraph is open, add the new elements to the paragraph // else this is the last paragraph @@ -728,8 +705,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager // finish last paragraph if it was closed with a linefeed if (lastElement.isPenalty() - && ((KnuthPenalty) lastElement).getP() - == -KnuthPenalty.INFINITE) { + && ((KnuthPenalty) lastElement).getP() == -KnuthPenalty.INFINITE) { // a penalty item whose value is -inf // represents a preserved linefeed, // which forces a line break @@ -737,7 +713,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager if (!lastPar.containsBox()) { //only a forced linefeed on this line //-> compensate with an auxiliary glue - lastPar.add(new KnuthGlue(iLineWidth, 0, iLineWidth, null, true)); + lastPar.add(new KnuthGlue(ipd, 0, ipd, null, true)); } lastPar.endParagraph(); ElementListObserver.observe(lastPar, "line", null); @@ -745,7 +721,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager if (log.isTraceEnabled()) { trace.append(" ]"); } - bPrevWasKnuthBox = false; + previousIsBox = false; } } else { // the sequence is a block sequence // the positions will be wrapped with this LM in postProcessLineBreaks @@ -767,144 +743,14 @@ public class LineLayoutManager extends InlineStackingLayoutManager } /** - * Find a set of breaking points. - * This method is called only once by getNextBreakPoss, and it - * subsequently calls the other findBreakingPoints() method with - * different parameters, until a set of breaking points is found. - * - * @param par the list of elements that must be parted - * into lines - * @param lineWidth the desired length ot the lines - */ - /* - private void findBreakingPoints(Paragraph par, int lineWidth) { - // maximum adjustment ratio permitted - float maxAdjustment = 1; - - // first try - if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) { - // the first try failed, now try something different - log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment); - if (hyphenationProperties.hyphenate == Constants.EN_TRUE) { - // consider every hyphenation point as a legal break - findHyphenationPoints(par); - } else { - // try with a higher threshold - maxAdjustment = 5; - } - - if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) { - // the second try failed too, try with a huge threshold; - // if this fails too, use a different algorithm - log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment - + (hyphenationProperties.hyphenate == Constants.EN_TRUE ? " and hyphenation" : "")); - maxAdjustment = 20; - if (!findBreakingPoints(par, lineWidth, maxAdjustment, true)) { - log.debug("No set of breaking points found, using first-fit algorithm"); - } - } - } - } - - private boolean findBreakingPoints(Paragraph par, int lineWidth, - double threshold, boolean force) { - KnuthParagraph knuthPara = new KnuthParagraph(par); - int lines = knuthPara.findBreakPoints(lineWidth, threshold, force); - if (lines == 0) { - return false; - } - - for (int i = lines-1; i >= 0; i--) { - int line = i+1; - if (log.isTraceEnabled()) { - log.trace("Making line from " + knuthPara.getStart(i) + " to " + - knuthPara.getEnd(i)); - } - // compute indent and adjustment ratio, according to - // the value of text-align and text-align-last - - int difference = knuthPara.getDifference(i); - if (line == lines) { - difference += par.lineFillerWidth; - } - int textAlign = (line < lines) - ? textAlignment : textAlignmentLast; - int indent = (textAlign == EN_CENTER) - ? difference / 2 - : (textAlign == EN_END) ? difference : 0; - indent += (line == 1 && knuthParagraphs.indexOf(par) == 0) - ? textIndent.getValue(this) : 0; - double ratio = (textAlign == EN_JUSTIFY) - ? knuthPara.getAdjustRatio(i) : 0; - - int start = knuthPara.getStart(i); - int end = knuthPara.getEnd(i); - makeLineBreakPosition(par, start, end, 0, ratio, indent); - } - return true; - } - - private void makeLineBreakPosition(Paragraph par, - int firstElementIndex, int lastElementIndex, - int insertIndex, double ratio, int indent) { - // line height calculation - - int halfLeading = (lineHeight - lead - follow) / 2; - // height above the main baseline - int lineLead = lead + halfLeading; - // maximum size of top and bottom alignment - int lineFollow = follow + halfLeading; - - ListIterator inlineIterator - = par.listIterator(firstElementIndex); - for (int j = firstElementIndex; - j <= lastElementIndex; - j++) { - KnuthElement element = (KnuthElement) inlineIterator.next(); - if (element.isBox()) { - KnuthInlineBox box = (KnuthInlineBox)element; - if (box.getLead() > lineLead) { - lineLead = box.getLead(); - } - if (box.getTotal() > lineFollow) { - lineFollow = box.getTotal(); - } - if (box.getMiddle() > lineLead + middleShift) { - lineLead += box.getMiddle() - - lineLead - middleShift; - } - if (box.getMiddle() > middlefollow - middleShift) { - middlefollow += box.getMiddle() - - middlefollow + middleShift; - } - } - } - - if (lineFollow - lineLead > middlefollow) { - middlefollow = lineFollow - lineLead; - } - - breakpoints.add(insertIndex, - new LineBreakPosition(this, - knuthParagraphs.indexOf(par), - lastElementIndex , - ratio, 0, indent, - lineLead + middlefollow, - lineLead)); - }*/ - - - /** * Phase 2 of Knuth algorithm: find optimal break points. * @param alignment alignment in BP direction of the paragraph * @param context the layout context * @return a list of Knuth elements representing broken lines */ private List createLineBreaks(int alignment, LayoutContext context) { - // find the optimal line breaking points for each paragraph - ListIterator paragraphsIterator - = knuthParagraphs.listIterator(knuthParagraphs.size()); + ListIterator paragraphsIterator = knuthParagraphs.listIterator(knuthParagraphs.size()); lineLayoutsList = new ArrayList(knuthParagraphs.size()); LineLayoutPossibilities llPoss; while (paragraphsIterator.hasPrevious()) { @@ -946,7 +792,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager this); if (hyphenationProperties.hyphenate.getEnum() == EN_TRUE - && fobj.getWrapOption() != EN_NO_WRAP) { + && fobj.getWrapOption() != EN_NO_WRAP && !hyphenationPerformed) { + hyphenationPerformed = true; findHyphenationPoints(currPar); } @@ -957,7 +804,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager } else { allowedBreaks = BreakingAlgorithm.NO_FLAGGED_PENALTIES; } - alg.setConstantLineWidth(iLineWidth); + alg.setConstantLineWidth(ipd); iBPcount = alg.findBreakingPoints(currPar, maxAdjustment, false, allowedBreaks); if (iBPcount == 0 || alignment == EN_JUSTIFY) { @@ -1013,26 +860,26 @@ public class LineLayoutManager extends InlineStackingLayoutManager alg.resetAlgorithm(); lineLayouts.savePossibilities(true); // try with shorter lines - int savedLineWidth = iLineWidth; - iLineWidth = (int) (iLineWidth * 0.95); + int savedLineWidth = ipd; + ipd = (int) (ipd * 0.95); iBPcount = alg.findBreakingPoints(currPar, - maxAdjustment, true, allowedBreaks); + maxAdjustment, true, allowedBreaks); // use normal lines, when possible lineLayouts.restorePossibilities(); - iLineWidth = savedLineWidth; + ipd = savedLineWidth; } if (!lineLayouts.canUseLessLines()) { alg.resetAlgorithm(); lineLayouts.savePossibilities(true); // try with longer lines - int savedLineWidth = iLineWidth; - iLineWidth = (int) (iLineWidth * 1.05); - alg.setConstantLineWidth(iLineWidth); + int savedLineWidth = ipd; + ipd = (int) (ipd * 1.05); + alg.setConstantLineWidth(ipd); iBPcount = alg.findBreakingPoints(currPar, maxAdjustment, true, allowedBreaks); // use normal lines, when possible lineLayouts.restorePossibilities(); - iLineWidth = savedLineWidth; + ipd = savedLineWidth; } //log.debug("LLM.getNextKnuthElements> now, layouts with more lines? " + lineLayouts.canUseMoreLines()); //log.debug(" now, layouts with fewer lines? " + lineLayouts.canUseLessLines()); @@ -1051,15 +898,16 @@ public class LineLayoutManager extends InlineStackingLayoutManager List returnList = new LinkedList(); + int endIndex = -1; for (int p = 0; p < knuthParagraphs.size(); p++) { // penalty between paragraphs if (p > 0) { - int strength = getKeepTogetherStrength(); - int penalty = KeepUtil.getPenaltyForKeep(strength); - if (penalty < KnuthElement.INFINITE) { - returnList.add(new BreakElement( - new Position(this), penalty, context)); - } + Keep keep = getKeepTogether(); + returnList.add(new BreakElement( + new Position(this), + keep.getPenalty(), + keep.getContext(), + context)); } LineLayoutPossibilities llPoss; @@ -1088,7 +936,6 @@ public class LineLayoutManager extends InlineStackingLayoutManager } else { /* "normal" vertical alignment: create a sequence whose boxes represent effective lines, and contain LineBreakPositions */ - Position returnPosition = new LeafPosition(this, p); int startIndex = 0; for (int i = 0; i < llPoss.getChosenLineCount(); @@ -1098,15 +945,14 @@ public class LineLayoutManager extends InlineStackingLayoutManager && i >= fobj.getOrphans() && i <= llPoss.getChosenLineCount() - fobj.getWidows()) { // penalty allowing a page break between lines - int strength = getKeepTogetherStrength(); - int penalty = KeepUtil.getPenaltyForKeep(strength); - if (penalty < KnuthElement.INFINITE) { - returnList.add(new BreakElement( - returnPosition, penalty, context)); - } + Keep keep = getKeepTogether(); + returnList.add(new BreakElement( + new LeafPosition(this, p, endIndex), + keep.getPenalty(), + keep.getContext(), + context)); } - int endIndex - = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos(); + endIndex = ((LineBreakPosition) llPoss.getChosenPosition(i)).getLeafPos(); // create a list of the FootnoteBodyLM handling footnotes // whose citations are in this line List footnoteList = new LinkedList(); @@ -1114,15 +960,14 @@ public class LineLayoutManager extends InlineStackingLayoutManager while (elementIterator.nextIndex() <= endIndex) { KnuthElement element = (KnuthElement) elementIterator.next(); if (element instanceof KnuthInlineBox - && ((KnuthInlineBox) element).isAnchor()) { + && ((KnuthInlineBox) element).isAnchor()) { footnoteList.add(((KnuthInlineBox) element).getFootnoteBodyLM()); } else if (element instanceof KnuthBlockBox) { footnoteList.addAll(((KnuthBlockBox) element).getFootnoteBodyLMs()); } } startIndex = endIndex + 1; - LineBreakPosition lbp - = (LineBreakPosition) llPoss.getChosenPosition(i); + LineBreakPosition lbp = (LineBreakPosition) llPoss.getChosenPosition(i); returnList.add(new KnuthBlockBox (lbp.lineHeight + lbp.spaceBefore + lbp.spaceAfter, footnoteList, lbp, false)); @@ -1283,28 +1128,43 @@ public class LineLayoutManager extends InlineStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - return ((BlockLevelLayoutManager) getParent()).getKeepTogetherStrength(); + public KeepProperty getKeepTogetherProperty() { + return ((BlockLevelLayoutManager) getParent()).getKeepTogetherProperty(); + } + + /** {@inheritDoc} */ + public KeepProperty getKeepWithPreviousProperty() { + return ((BlockLevelLayoutManager) getParent()).getKeepWithPreviousProperty(); + } + + /** {@inheritDoc} */ + public KeepProperty getKeepWithNextProperty() { + return ((BlockLevelLayoutManager) getParent()).getKeepWithNextProperty(); + } + + /** {@inheritDoc} */ + public Keep getKeepTogether() { + return ((BlockLevelLayoutManager) getParent()).getKeepTogether(); } /** {@inheritDoc} */ public boolean mustKeepWithPrevious() { - return getKeepWithPreviousStrength() > KEEP_AUTO; + return !getKeepWithPrevious().isAuto(); } /** {@inheritDoc} */ public boolean mustKeepWithNext() { - return getKeepWithNextStrength() > KEEP_AUTO; + return !getKeepWithNext().isAuto(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ @@ -1409,6 +1269,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager break; } //TODO Something's not right here. See block_hyphenation_linefeed_preserve.xml + //for more info: see also https://issues.apache.org/bugzilla/show_bug.cgi?id=38264 // collect word fragments, ignoring auxiliary elements; // each word fragment was created by a different TextLM @@ -1580,7 +1441,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager Position pos = (Position) parentIter.next(); boolean isLastPosition = !parentIter.hasNext(); if (pos instanceof LineBreakPosition) { - addInlineArea(context, pos, isLastPosition); + addInlineArea(context, (LineBreakPosition) pos, isLastPosition); } else if ((pos instanceof NonLeafPosition) && pos.generatesAreas()) { addBlockArea(context, pos, isLastPosition); } else { @@ -1600,147 +1461,129 @@ public class LineLayoutManager extends InlineStackingLayoutManager * @param pos the position for which the line is generated * @param isLastPosition true if this is the last position of this LM */ - private void addInlineArea(LayoutContext context, Position pos, boolean isLastPosition) { - ListIterator seqIterator = null; - KnuthElement tempElement = null; - // the TLM which created the last KnuthElement in this line - LayoutManager lastLM = null; - - LineBreakPosition lbp = (LineBreakPosition) pos; - int iCurrParIndex; - iCurrParIndex = lbp.iParIndex; - KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(iCurrParIndex); - int iStartElement = lbp.iStartIndex; - int iEndElement = lbp.getLeafPos(); - - LineArea lineArea - = new LineArea((lbp.getLeafPos() < seq.size() - 1 - ? textAlignment : textAlignmentLast), - lbp.difference, lbp.availableStretch, lbp.availableShrink); - if (lbp.startIndent != 0) { - lineArea.addTrait(Trait.START_INDENT, new Integer(lbp.startIndent)); - } - lineArea.setBPD(lbp.lineHeight); - lineArea.setIPD(lbp.lineWidth); - lineArea.addTrait(Trait.SPACE_BEFORE, new Integer(lbp.spaceBefore)); - lineArea.addTrait(Trait.SPACE_AFTER, new Integer(lbp.spaceAfter)); - alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline); - - if (seq instanceof Paragraph) { - Paragraph currPar = (Paragraph) seq; - // ignore the first elements added by the LineLayoutManager - iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0; - - // if this is the last line area that for this paragraph, - // ignore the last elements added by the LineLayoutManager and - // subtract the last-line-end-indent from the area ipd - if (iEndElement == (currPar.size() - 1)) { - iEndElement -= currPar.ignoreAtEnd; - lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this)); - } + private void addInlineArea(LayoutContext context, LineBreakPosition lbp, + boolean isLastPosition) { + // the TLM which created the last KnuthElement in this line + LayoutManager lastLM = null; + + KnuthSequence seq = (KnuthSequence) knuthParagraphs.get(lbp.parIndex); + int startElementIndex = lbp.startIndex; + int endElementIndex = lbp.getLeafPos(); + + LineArea lineArea = new LineArea( + (lbp.getLeafPos() < seq.size() - 1 ? textAlignment : textAlignmentLast), + lbp.difference, lbp.availableStretch, lbp.availableShrink); + if (lbp.startIndent != 0) { + lineArea.addTrait(Trait.START_INDENT, new Integer(lbp.startIndent)); + } + lineArea.setBPD(lbp.lineHeight); + lineArea.setIPD(lbp.lineWidth); + lineArea.addTrait(Trait.SPACE_BEFORE, new Integer(lbp.spaceBefore)); + lineArea.addTrait(Trait.SPACE_AFTER, new Integer(lbp.spaceAfter)); + alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline); + + if (seq instanceof Paragraph) { + Paragraph currPar = (Paragraph) seq; + // ignore the first elements added by the LineLayoutManager + startElementIndex += (startElementIndex == 0) ? currPar.ignoreAtStart : 0; + + // if this is the last line area that for this paragraph, + // ignore the last elements added by the LineLayoutManager and + // subtract the last-line-end-indent from the area ipd + if (endElementIndex == (currPar.size() - 1)) { + endElementIndex -= currPar.ignoreAtEnd; + lineArea.setIPD(lineArea.getIPD() - lastLineEndIndent.getValue(this)); } + } - // Remove trailing spaces if allowed so - if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED + // Remove trailing spaces if allowed so + if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED || whiteSpaceTreament == EN_IGNORE || whiteSpaceTreament == EN_IGNORE_IF_BEFORE_LINEFEED) { - // ignore the last element in the line if it is a KnuthGlue object - seqIterator = seq.listIterator(iEndElement); - tempElement = (KnuthElement) seqIterator.next(); - if (tempElement.isGlue()) { - iEndElement--; - // this returns the same KnuthElement - seqIterator.previous(); - if (seqIterator.hasPrevious()) { - tempElement = (KnuthElement) seqIterator.previous(); - } else { - tempElement = null; - } - } - if (tempElement != null) { - lastLM = tempElement.getLayoutManager(); + // ignore the last element in the line if it is a KnuthGlue object + ListIterator seqIterator = seq.listIterator(endElementIndex); + KnuthElement lastElement = (KnuthElement) seqIterator.next(); + lastLM = lastElement.getLayoutManager(); + if (lastElement.isGlue()) { + endElementIndex--; + // this returns the same KnuthElement + seqIterator.previous(); + if (seqIterator.hasPrevious()) { + lastLM = ((KnuthElement) seqIterator.previous()).getLayoutManager(); } } + } - // Remove leading spaces if allowed so - if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED + // Remove leading spaces if allowed so + if (whiteSpaceTreament == EN_IGNORE_IF_SURROUNDING_LINEFEED || whiteSpaceTreament == EN_IGNORE || whiteSpaceTreament == EN_IGNORE_IF_AFTER_LINEFEED) { - // ignore KnuthGlue and KnuthPenalty objects - // at the beginning of the line - seqIterator = seq.listIterator(iStartElement); - tempElement = (KnuthElement) seqIterator.next(); - while (!tempElement.isBox() && seqIterator.hasNext()) { - tempElement = (KnuthElement) seqIterator.next(); - iStartElement++; - } - } - // Add the inline areas to lineArea - PositionIterator inlinePosIter - = new KnuthPossPosIter(seq, iStartElement, iEndElement + 1); - - iStartElement = lbp.getLeafPos() + 1; - if (iStartElement == seq.size()) { - // advance to next paragraph - iStartElement = 0; + // ignore KnuthGlue and KnuthPenalty objects + // at the beginning of the line + ListIterator seqIterator = seq.listIterator(startElementIndex); + while (seqIterator.hasNext() && !((KnuthElement) seqIterator.next()).isBox()) { + startElementIndex++; } + } + // Add the inline areas to lineArea + PositionIterator inlinePosIter = new KnuthPossPosIter(seq, startElementIndex, + endElementIndex + 1); - LayoutContext lc = new LayoutContext(0); - lc.setAlignmentContext(alignmentContext); - lc.setSpaceAdjust(lbp.dAdjust); - lc.setIPDAdjust(lbp.ipdAdjust); - lc.setLeadingSpace(new SpaceSpecifier(true)); - lc.setTrailingSpace(new SpaceSpecifier(false)); - lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); - - /* - * extension (not in the XSL FO recommendation): if the left and right margins - * have been optimized, recompute indents and / or adjust ratio, according - * to the paragraph horizontal alignment - */ - if (false && textAlignment == EN_JUSTIFY) { - // re-compute space adjust ratio - int updatedDifference = context.getStackLimitIP().opt - - lbp.lineWidth + lbp.difference; - double updatedRatio = 0.0; - if (updatedDifference > 0) { - updatedRatio = (float) updatedDifference / lbp.availableStretch; - } else if (updatedDifference < 0) { - updatedRatio = (float) updatedDifference / lbp.availableShrink; - } - lc.setIPDAdjust(updatedRatio); - //log.debug("LLM.addAreas> old difference = " + lbp.difference + " new difference = " + updatedDifference); - //log.debug(" old ratio = " + lbp.ipdAdjust + " new ratio = " + updatedRatio); - } else if (false && textAlignment == EN_CENTER) { - // re-compute indent - int updatedIndent = lbp.startIndent - + (context.getStackLimitIP().opt - lbp.lineWidth) / 2; - lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent)); - } else if (false && textAlignment == EN_END) { - // re-compute indent - int updatedIndent = lbp.startIndent - + (context.getStackLimitIP().opt - lbp.lineWidth); - lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent)); - } + LayoutContext lc = new LayoutContext(0); + lc.setAlignmentContext(alignmentContext); + lc.setSpaceAdjust(lbp.dAdjust); + lc.setIPDAdjust(lbp.ipdAdjust); + lc.setLeadingSpace(new SpaceSpecifier(true)); + lc.setTrailingSpace(new SpaceSpecifier(false)); + lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true); - setCurrentArea(lineArea); - setChildContext(lc); - LayoutManager childLM; - while ((childLM = inlinePosIter.getNextChildLM()) != null) { - lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM)); - childLM.addAreas(inlinePosIter, lc); - lc.setLeadingSpace(lc.getTrailingSpace()); - lc.setTrailingSpace(new SpaceSpecifier(false)); + /* + * extension (not in the XSL FO recommendation): if the left and right margins + * have been optimized, recompute indents and / or adjust ratio, according + * to the paragraph horizontal alignment + */ + if (false && textAlignment == EN_JUSTIFY) { + // re-compute space adjust ratio + int updatedDifference = context.getRefIPD() + - lbp.lineWidth + lbp.difference; + double updatedRatio = 0.0; + if (updatedDifference > 0) { + updatedRatio = (float) updatedDifference / lbp.availableStretch; + } else if (updatedDifference < 0) { + updatedRatio = (float) updatedDifference / lbp.availableShrink; } + lc.setIPDAdjust(updatedRatio); + //log.debug("LLM.addAreas> old difference = " + lbp.difference + " new difference = " + updatedDifference); + //log.debug(" old ratio = " + lbp.ipdAdjust + " new ratio = " + updatedRatio); + } else if (false && textAlignment == EN_CENTER) { + // re-compute indent + int updatedIndent = lbp.startIndent + + (context.getRefIPD() - lbp.lineWidth) / 2; + lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent)); + } else if (false && textAlignment == EN_END) { + // re-compute indent + int updatedIndent = lbp.startIndent + + (context.getRefIPD() - lbp.lineWidth); + lineArea.addTrait(Trait.START_INDENT, new Integer(updatedIndent)); + } - // when can this be null? - // if display-align is distribute, add space after - if (context.getSpaceAfter() > 0 - && (!context.isLastArea() || !isLastPosition)) { - lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter()); - } - lineArea.finalise(); - parentLM.addChildArea(lineArea); + setCurrentArea(lineArea); + setChildContext(lc); + LayoutManager childLM; + while ((childLM = inlinePosIter.getNextChildLM()) != null) { + lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM)); + childLM.addAreas(inlinePosIter, lc); + lc.setLeadingSpace(lc.getTrailingSpace()); + lc.setTrailingSpace(new SpaceSpecifier(false)); + } + + // if display-align is distribute, add space after + if (context.getSpaceAfter() > 0 + && (!context.isLastArea() || !isLastPosition)) { + lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter()); + } + lineArea.finalise(); + parentLM.addChildArea(lineArea); } /** @@ -1783,7 +1626,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager // set last area flag blocklc.setFlags(LayoutContext.LAST_AREA, (context.isLastArea() && childLM == lastLM)); - blocklc.setStackLimitsFrom(context); + blocklc.setStackLimitBP(context.getStackLimitBP()); // Add the line areas to Area childLM.addAreas(childPosIter, blocklc); blocklc.setLeadingSpace(blocklc.getTrailingSpace()); @@ -1824,5 +1667,11 @@ public class LineLayoutManager extends InlineStackingLayoutManager public boolean getGeneratesLineArea() { return true; } + + /** {@inheritDoc} */ + public boolean isRestartable() { + return true; + } + } diff --git a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java index 1cd3ab9d2..a9f2eeb27 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java @@ -29,10 +29,10 @@ import org.apache.commons.logging.LogFactory; import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.fo.flow.ListBlock; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.ConditionalElementListener; import org.apache.fop.layoutmgr.ElementListUtils; -import org.apache.fop.layoutmgr.KeepUtil; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.NonLeafPosition; @@ -279,21 +279,18 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KeepUtil.getCombinedBlockLevelKeepStrength( - getListBlockFO().getKeepTogether()); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getListBlockFO().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getListBlockFO().getKeepWithNext()); + public KeepProperty getKeepWithPreviousProperty() { + return getListBlockFO().getKeepWithPrevious(); } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getListBlockFO().getKeepWithPrevious()); + public KeepProperty getKeepWithNextProperty() { + return getListBlockFO().getKeepWithNext(); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java index 0a2dec945..7fd2219ea 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java @@ -28,8 +28,9 @@ import org.apache.fop.area.Block; import org.apache.fop.fo.flow.AbstractListItemPart; import org.apache.fop.fo.flow.ListItemBody; import org.apache.fop.fo.flow.ListItemLabel; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.NonLeafPosition; @@ -221,20 +222,18 @@ public class ListItemContentLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KeepUtil.getCombinedBlockLevelKeepStrength(getPartFO().getKeepTogether()); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getPartFO().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; } } diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java index a4e8982f7..fb88bb79d 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java @@ -32,13 +32,13 @@ import org.apache.fop.area.Block; import org.apache.fop.fo.flow.ListItem; import org.apache.fop.fo.flow.ListItemBody; import org.apache.fop.fo.flow.ListItemLabel; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ConditionalElementListener; import org.apache.fop.layoutmgr.ElementListObserver; import org.apache.fop.layoutmgr.ElementListUtils; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBlockBox; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; @@ -83,8 +83,8 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager private MinOptMax effSpaceBefore; private MinOptMax effSpaceAfter; - private int keepWithNextPendingOnLabel; - private int keepWithNextPendingOnBody; + private Keep keepWithNextPendingOnLabel; + private Keep keepWithNextPendingOnBody; private int listItemHeight; @@ -254,8 +254,8 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager context.updateKeepWithNextPending(this.keepWithNextPendingOnLabel); context.updateKeepWithNextPending(this.keepWithNextPendingOnBody); - context.updateKeepWithNextPending(getKeepWithNextStrength()); - context.updateKeepWithPreviousPending(getKeepWithPreviousStrength()); + context.updateKeepWithNextPending(getKeepWithNext()); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); setFinished(true); resetSpaces(); @@ -276,16 +276,16 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager int totalHeight = Math.max(fullHeights[0], fullHeights[1]); int step; int addedBoxHeight = 0; - int keepWithNextActive = BlockLevelLayoutManager.KEEP_AUTO; + Keep keepWithNextActive = Keep.KEEP_AUTO; LinkedList returnList = new LinkedList(); while ((step = getNextStep(elementLists, start, end, partialHeights)) > 0) { if (end[0] + 1 == elementLists[0].size()) { - keepWithNextActive = Math.max(keepWithNextActive, keepWithNextPendingOnLabel); + keepWithNextActive = keepWithNextActive.compare(keepWithNextPendingOnLabel); } if (end[1] + 1 == elementLists[1].size()) { - keepWithNextActive = Math.max(keepWithNextActive, keepWithNextPendingOnBody); + keepWithNextActive = keepWithNextActive.compare(keepWithNextPendingOnBody); } // compute penalty height and box height @@ -339,14 +339,13 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager } if (addedBoxHeight < totalHeight) { - int strength = BlockLevelLayoutManager.KEEP_AUTO; - strength = Math.max(strength, keepWithNextActive); - strength = Math.max(strength, getKeepTogetherStrength()); + Keep keep = keepWithNextActive.compare(getKeepTogether()); int p = stepPenalty; if (p > -KnuthElement.INFINITE) { - p = Math.max(p, KeepUtil.getPenaltyForKeep(strength)); + p = Math.max(p, keep.getPenalty()); } - returnList.add(new BreakElement(stepPosition, penaltyHeight, p, -1, context)); + returnList.add(new BreakElement(stepPosition, penaltyHeight, p, keep.getContext(), + context)); } } @@ -644,21 +643,18 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KeepUtil.getCombinedBlockLevelKeepStrength( - getListItemFO().getKeepTogether()); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getListItemFO().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getListItemFO().getKeepWithNext()); + public KeepProperty getKeepWithPreviousProperty() { + return getListItemFO().getKeepWithPrevious(); } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getListItemFO().getKeepWithPrevious()); + public KeepProperty getKeepWithNextProperty() { + return getListItemFO().getKeepWithNext(); } /** {@inheritDoc} */ @@ -706,6 +702,13 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager } } + /** {@inheritDoc} */ + public void reset() { + super.reset(); + label.reset(); + body.reset(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/table/ActiveCell.java b/src/java/org/apache/fop/layoutmgr/table/ActiveCell.java index a9da7a3ca..53e798e3c 100644 --- a/src/java/org/apache/fop/layoutmgr/table/ActiveCell.java +++ b/src/java/org/apache/fop/layoutmgr/table/ActiveCell.java @@ -19,19 +19,21 @@ package org.apache.fop.layoutmgr.table; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.table.ConditionalBorder; import org.apache.fop.fo.flow.table.EffRow; import org.apache.fop.fo.flow.table.PrimaryGridUnit; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBlockBox; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; @@ -73,7 +75,7 @@ class ActiveCell { /** True if the next CellPart that will be created will be the last one for this cell. */ private boolean lastCellPart; - private int keepWithNextStrength; + private Keep keepWithNext; private int spanIndex = 0; @@ -133,7 +135,12 @@ class ActiveCell { this.totalLength = other.totalLength; this.penaltyLength = other.penaltyLength; this.penaltyValue = other.penaltyValue; - this.footnoteList = other.footnoteList; + if (other.footnoteList != null) { + if (this.footnoteList == null) { + this.footnoteList = new ArrayList(); + } + this.footnoteList.addAll(other.footnoteList); + } this.condBeforeContentLength = other.condBeforeContentLength; this.breakClass = other.breakClass; } @@ -211,7 +218,7 @@ class ActiveCell { includedLength = -1; // Avoid troubles with cells having content of zero length totalLength = previousRowsLength + ElementListUtils.calcContentLength(elementList); endRowIndex = rowIndex + pgu.getCell().getNumberRowsSpanned() - 1; - keepWithNextStrength = BlockLevelLayoutManager.KEEP_AUTO; + keepWithNext = Keep.KEEP_AUTO; remainingLength = totalLength - previousRowsLength; afterNextStep = new Step(previousRowsLength); @@ -297,7 +304,9 @@ class ActiveCell { afterNextStep.penaltyValue = 0; afterNextStep.condBeforeContentLength = 0; afterNextStep.breakClass = Constants.EN_AUTO; - afterNextStep.footnoteList = null; + if (afterNextStep.footnoteList != null) { + afterNextStep.footnoteList.clear(); + } boolean breakFound = false; boolean prevIsBox = false; boolean boxFound = false; @@ -305,7 +314,11 @@ class ActiveCell { KnuthElement el = (KnuthElement) knuthIter.next(); if (el.isPenalty()) { prevIsBox = false; - if (el.getP() < KnuthElement.INFINITE) { + if (el.getP() < KnuthElement.INFINITE + || ((KnuthPenalty) el).getBreakClass() == Constants.EN_PAGE) { + // TODO too much is being done in that test, only to handle + // keep.within-column properly. + // First legal break point breakFound = true; KnuthPenalty p = (KnuthPenalty) el; @@ -524,7 +537,7 @@ class ActiveCell { */ CellPart createCellPart() { if (nextStep.end + 1 == elementList.size()) { - keepWithNextStrength = pgu.getKeepWithNextStrength(); + keepWithNext = pgu.getKeepWithNext(); // TODO if keep-with-next is set on the row, must every cell of the row // contribute some content from children blocks? // see http://mail-archives.apache.org/mod_mbox/xmlgraphics-fop-dev/200802.mbox/ @@ -563,11 +576,12 @@ class ActiveCell { void addFootnotes(List footnoteList) { if (includedInLastStep() && nextStep.footnoteList != null) { footnoteList.addAll(nextStep.footnoteList); + nextStep.footnoteList.clear(); } } - int getKeepWithNextStrength() { - return keepWithNextStrength; + Keep getKeepWithNext() { + return keepWithNext; } int getPenaltyValue() { diff --git a/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java index 54cb1ebfe..83e71bb21 100644 --- a/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/RowGroupLayoutManager.java @@ -60,8 +60,8 @@ class RowGroupLayoutManager { LinkedList returnList = new LinkedList(); createElementsForRowGroup(context, alignment, bodyType, returnList); - context.updateKeepWithPreviousPending(rowGroup[0].getKeepWithPreviousStrength()); - context.updateKeepWithNextPending(rowGroup[rowGroup.length - 1].getKeepWithNextStrength()); + context.updateKeepWithPreviousPending(rowGroup[0].getKeepWithPrevious()); + context.updateKeepWithNextPending(rowGroup[rowGroup.length - 1].getKeepWithNext()); int breakBefore = Constants.EN_AUTO; TableRow firstRow = rowGroup[0].getTableRow(); diff --git a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java index 4cf68b97b..edf73acab 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableAndCaptionLayoutManager.java @@ -23,6 +23,7 @@ import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.fo.flow.table.TableAndCaption; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; @@ -201,19 +202,8 @@ public class TableAndCaptionLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KEEP_AUTO; - /* TODO Complete me! - int strength = KeepUtil.getCombinedBlockLevelKeepStrength( - getTableAndCaptionFO().getKeepTogether()); - */ - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; - } - - /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; /* TODO Complete me! return KeepUtil.getCombinedBlockLevelKeepStrength( getTableAndCaptionFO().getKeepWithNext()); @@ -221,12 +211,12 @@ public class TableAndCaptionLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; /* TODO Complete me! return KeepUtil.getCombinedBlockLevelKeepStrength( getTableAndCaptionFO().getKeepWithPrevious()); */ } -}
\ No newline at end of file +} diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java index 071082624..9d9255e0c 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCaptionLayoutManager.java @@ -23,6 +23,7 @@ import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.fo.flow.table.TableCaption; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.LayoutContext; import org.apache.fop.layoutmgr.PositionIterator; @@ -197,21 +198,8 @@ public class TableCaptionLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KEEP_AUTO; - /* TODO Complete me! - strength = Math.max(strength, KeepUtil.getKeepStrength( - getTableCaptionFO().getKeepTogether().getWithinPage())); - strength = Math.max(strength, KeepUtil.getKeepStrength( - getTableCaptionFO().getKeepTogether().getWithinColumn())); - */ - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; - } - - /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; /* TODO Complete me! return KeepUtil.getCombinedBlockLevelKeepStrength( getTableCaptionFO().getKeepWithNext()); @@ -219,8 +207,8 @@ public class TableCaptionLayoutManager extends BlockStackingLayoutManager { } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; /* TODO Complete me! return KeepUtil.getCombinedBlockLevelKeepStrength( getTableCaptionFO().getKeepWithPrevious()); diff --git a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java index 239a1a88e..4e3c0b102 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableCellLayoutManager.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.Trait; @@ -31,21 +32,23 @@ import org.apache.fop.fo.flow.table.ConditionalBorder; import org.apache.fop.fo.flow.table.GridUnit; import org.apache.fop.fo.flow.table.PrimaryGridUnit; import org.apache.fop.fo.flow.table.Table; -import org.apache.fop.fo.flow.table.TablePart; import org.apache.fop.fo.flow.table.TableCell; import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.flow.table.TablePart; import org.apache.fop.fo.flow.table.TableRow; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo; import org.apache.fop.layoutmgr.AreaAdditionUtil; import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.ElementListUtils; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; import org.apache.fop.layoutmgr.KnuthPenalty; import org.apache.fop.layoutmgr.LayoutContext; +import org.apache.fop.layoutmgr.LayoutManager; import org.apache.fop.layoutmgr.Position; import org.apache.fop.layoutmgr.PositionIterator; import org.apache.fop.layoutmgr.SpaceResolver; @@ -138,9 +141,9 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager List contentList = new LinkedList(); List returnList = new LinkedList(); - BlockLevelLayoutManager curLM; // currently active LM - BlockLevelLayoutManager prevLM = null; // previously active LM - while ((curLM = (BlockLevelLayoutManager) getChildLM()) != null) { + LayoutManager curLM; // currently active LM + LayoutManager prevLM = null; // previously active LM + while ((curLM = getChildLM()) != null) { LayoutContext childLC = new LayoutContext(0); // curLM is a ? childLC.setStackLimitBP(MinOptMax.subtract(context @@ -153,11 +156,12 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager log.debug("child LM signals pending keep with next"); } if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) { - primaryGridUnit.setKeepWithPreviousStrength(childLC.getKeepWithPreviousPending()); + primaryGridUnit.setKeepWithPrevious(childLC.getKeepWithPreviousPending()); childLC.clearKeepWithPreviousPending(); } - if (prevLM != null) { + if (prevLM != null + && !ElementListUtils.endsWithForcedBreak(contentList)) { // there is a block handled by prevLM // before the one handled by curLM addInBetweenBreak(contentList, context, childLC); @@ -174,7 +178,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } prevLM = curLM; } - primaryGridUnit.setKeepWithNextStrength(context.getKeepWithNextPending()); + primaryGridUnit.setKeepWithNext(context.getKeepWithNextPending()); returnedList = new LinkedList(); if (!contentList.isEmpty()) { @@ -195,7 +199,7 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } final KnuthElement lastItem = (KnuthElement) ListUtil .getLast(returnList); - if (((KnuthElement) lastItem).isForcedBreak()) { + if (lastItem.isForcedBreak()) { KnuthPenalty p = (KnuthPenalty) lastItem; primaryGridUnit.setBreakAfter(p.getBreakClass()); p.setP(0); @@ -556,26 +560,23 @@ public class TableCellLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KEEP_AUTO; + public Keep getKeepTogether() { + Keep keep = Keep.KEEP_AUTO; if (primaryGridUnit.getRow() != null) { - strength = Math.max(strength, KeepUtil.getKeepStrength( - primaryGridUnit.getRow().getKeepTogether().getWithinPage())); - strength = Math.max(strength, KeepUtil.getKeepStrength( - primaryGridUnit.getRow().getKeepTogether().getWithinColumn())); + keep = Keep.getKeep(primaryGridUnit.getRow().getKeepTogether()); } - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + keep = keep.compare(getParentKeepTogether()); + return keep; } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-next!) + public Keep getKeepWithNext() { + return Keep.KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-next!) } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-previous!) + public Keep getKeepWithPrevious() { + return Keep.KEEP_AUTO; //TODO FIX ME (table-cell has no keep-with-previous!) } // --------- Property Resolution related functions --------- // diff --git a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java index 2ff97a6a7..40ebf9e0d 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableContentLayoutManager.java @@ -35,10 +35,9 @@ import org.apache.fop.fo.flow.table.EffRow; import org.apache.fop.fo.flow.table.PrimaryGridUnit; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TablePart; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ElementListUtils; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBox; import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; @@ -213,13 +212,13 @@ public class TableContentLayoutManager implements PercentBaseContext { context.clearKeepsPending(); context.setBreakBefore(Constants.EN_AUTO); context.setBreakAfter(Constants.EN_AUTO); - int keepWithPrevious = BlockLevelLayoutManager.KEEP_AUTO; + Keep keepWithPrevious = Keep.KEEP_AUTO; int breakBefore = Constants.EN_AUTO; if (rowGroup != null) { RowGroupLayoutManager rowGroupLM = new RowGroupLayoutManager(getTableLM(), rowGroup, stepper); List nextRowGroupElems = rowGroupLM.getNextKnuthElements(context, alignment, bodyType); - keepWithPrevious = Math.max(keepWithPrevious, context.getKeepWithPreviousPending()); + keepWithPrevious = keepWithPrevious.compare(context.getKeepWithPreviousPending()); breakBefore = context.getBreakBefore(); int breakBetween = context.getBreakAfter(); returnList.addAll(nextRowGroupElems); @@ -228,7 +227,7 @@ public class TableContentLayoutManager implements PercentBaseContext { //Note previous pending keep-with-next and clear the strength //(as the layout context is reused) - int keepWithNextPending = context.getKeepWithNextPending(); + Keep keepWithNextPending = context.getKeepWithNextPending(); context.clearKeepWithNextPending(); //Get elements for next row group @@ -246,17 +245,17 @@ public class TableContentLayoutManager implements PercentBaseContext { */ //Determine keep constraints - int penaltyStrength = BlockLevelLayoutManager.KEEP_AUTO; - penaltyStrength = Math.max(penaltyStrength, keepWithNextPending); - penaltyStrength = Math.max(penaltyStrength, context.getKeepWithPreviousPending()); + Keep keep = keepWithNextPending.compare(context.getKeepWithPreviousPending()); context.clearKeepWithPreviousPending(); - penaltyStrength = Math.max(penaltyStrength, getTableLM().getKeepTogetherStrength()); - int penaltyValue = KeepUtil.getPenaltyForKeep(penaltyStrength); + keep = keep.compare(getTableLM().getKeepTogether()); + int penaltyValue = keep.getPenalty(); + int breakClass = keep.getContext(); breakBetween = BreakUtil.compareBreakClasses(breakBetween, context.getBreakBefore()); if (breakBetween != Constants.EN_AUTO) { penaltyValue = -KnuthElement.INFINITE; + breakClass = breakBetween; } BreakElement breakElement; ListIterator elemIter = returnList.listIterator(returnList.size()); @@ -267,7 +266,7 @@ public class TableContentLayoutManager implements PercentBaseContext { breakElement = (BreakElement) elem; } breakElement.setPenaltyValue(penaltyValue); - breakElement.setBreakClass(breakBetween); + breakElement.setBreakClass(breakClass); returnList.addAll(nextRowGroupElems); breakBetween = context.getBreakAfter(); } diff --git a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java index dc2b3cc46..9ccca8b9e 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableLayoutManager.java @@ -35,11 +35,11 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; +import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.layoutmgr.BlockLevelEventProducer; import org.apache.fop.layoutmgr.BlockStackingLayoutManager; import org.apache.fop.layoutmgr.BreakElement; import org.apache.fop.layoutmgr.ConditionalElementListener; -import org.apache.fop.layoutmgr.KeepUtil; import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; import org.apache.fop.layoutmgr.LayoutContext; @@ -256,10 +256,10 @@ public class TableLayoutManager extends BlockStackingLayoutManager log.debug(contentKnuthElements); wrapPositionElements(contentKnuthElements, returnList); - context.updateKeepWithPreviousPending(getKeepWithPreviousStrength()); + context.updateKeepWithPreviousPending(getKeepWithPrevious()); context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending()); - context.updateKeepWithNextPending(getKeepWithNextStrength()); + context.updateKeepWithNextPending(getKeepWithNext()); context.updateKeepWithNextPending(childLC.getKeepWithNextPending()); if (getTable().isSeparateBorderModel()) { @@ -448,20 +448,18 @@ public class TableLayoutManager extends BlockStackingLayoutManager } /** {@inheritDoc} */ - public int getKeepTogetherStrength() { - int strength = KeepUtil.getCombinedBlockLevelKeepStrength(getTable().getKeepTogether()); - strength = Math.max(strength, getParentKeepTogetherStrength()); - return strength; + public KeepProperty getKeepTogetherProperty() { + return getTable().getKeepTogether(); } /** {@inheritDoc} */ - public int getKeepWithNextStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getTable().getKeepWithNext()); + public KeepProperty getKeepWithPreviousProperty() { + return getTable().getKeepWithPrevious(); } /** {@inheritDoc} */ - public int getKeepWithPreviousStrength() { - return KeepUtil.getCombinedBlockLevelKeepStrength(getTable().getKeepWithPrevious()); + public KeepProperty getKeepWithNextProperty() { + return getTable().getKeepWithNext(); } // --------- Property Resolution related functions --------- // diff --git a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java index a3fba279e..92a641ed0 100644 --- a/src/java/org/apache/fop/layoutmgr/table/TableStepper.java +++ b/src/java/org/apache/fop/layoutmgr/table/TableStepper.java @@ -30,12 +30,10 @@ import org.apache.fop.fo.Constants; import org.apache.fop.fo.flow.table.EffRow; import org.apache.fop.fo.flow.table.GridUnit; import org.apache.fop.fo.flow.table.PrimaryGridUnit; -import org.apache.fop.layoutmgr.BlockLevelLayoutManager; import org.apache.fop.layoutmgr.BreakElement; -import org.apache.fop.layoutmgr.KeepUtil; +import org.apache.fop.layoutmgr.Keep; import org.apache.fop.layoutmgr.KnuthBlockBox; import org.apache.fop.layoutmgr.KnuthBox; -import org.apache.fop.layoutmgr.KnuthElement; import org.apache.fop.layoutmgr.KnuthGlue; import org.apache.fop.layoutmgr.KnuthPenalty; import org.apache.fop.layoutmgr.LayoutContext; @@ -241,40 +239,38 @@ public class TableStepper { } } - int strength = BlockLevelLayoutManager.KEEP_AUTO; + Keep keep = Keep.KEEP_AUTO; int stepPenalty = 0; for (Iterator iter = activeCells.iterator(); iter.hasNext();) { ActiveCell activeCell = (ActiveCell) iter.next(); - strength = Math.max(strength, activeCell.getKeepWithNextStrength()); + keep = keep.compare(activeCell.getKeepWithNext()); stepPenalty = Math.max(stepPenalty, activeCell.getPenaltyValue()); } if (!rowFinished) { - strength = Math.max(strength, rowGroup[activeRowIndex].getKeepTogetherStrength()); + keep = keep.compare(rowGroup[activeRowIndex].getKeepTogether()); //The above call doesn't take the penalty from the table into account, so... - strength = Math.max(strength, getTableLM().getKeepTogetherStrength()); + keep = keep.compare(getTableLM().getKeepTogether()); } else if (activeRowIndex < rowGroup.length - 1) { - strength = Math.max(strength, - rowGroup[activeRowIndex].getKeepWithNextStrength()); - strength = Math.max(strength, - rowGroup[activeRowIndex + 1].getKeepWithPreviousStrength()); + keep = keep.compare(rowGroup[activeRowIndex].getKeepWithNext()); + keep = keep.compare(rowGroup[activeRowIndex + 1].getKeepWithPrevious()); nextBreakClass = BreakUtil.compareBreakClasses(nextBreakClass, rowGroup[activeRowIndex].getBreakAfter()); nextBreakClass = BreakUtil.compareBreakClasses(nextBreakClass, rowGroup[activeRowIndex + 1].getBreakBefore()); } - int p = KeepUtil.getPenaltyForKeep(strength); + int p = keep.getPenalty(); if (rowHeightSmallerThanFirstStep) { rowHeightSmallerThanFirstStep = false; p = KnuthPenalty.INFINITE; } - if (p > -KnuthElement.INFINITE) { - p = Math.max(p, stepPenalty); - } + p = Math.max(p, stepPenalty); + int breakClass = keep.getContext(); if (nextBreakClass != Constants.EN_AUTO) { log.trace("Forced break encountered"); p = -KnuthPenalty.INFINITE; //Overrides any keeps (see 4.8 in XSL 1.0) + breakClass = nextBreakClass; } - returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, nextBreakClass, context)); + returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, breakClass, context)); if (penaltyOrGlueLen < 0) { returnList.add(new KnuthGlue(-penaltyOrGlueLen, 0, 0, new Position(null), true)); } diff --git a/src/java/org/apache/fop/pdf/PDFColor.java b/src/java/org/apache/fop/pdf/PDFColor.java index 5bac9532c..f2ae2da33 100644 --- a/src/java/org/apache/fop/pdf/PDFColor.java +++ b/src/java/org/apache/fop/pdf/PDFColor.java @@ -541,13 +541,8 @@ public class PDFColor extends PDFPathPaint { return (new byte[0]); } - /** - * Check for equality of color with another object. - * - * @param obj the object to compare - * @return true if colors are equal - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (!(obj instanceof PDFColor)) { return false; } diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 9bc4c0d2e..46effdfd6 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -526,8 +526,8 @@ public class PDFDocument { private Object findPDFObject(List list, PDFObject compare) { for (Iterator iter = list.iterator(); iter.hasNext();) { - Object obj = iter.next(); - if (compare.equals(obj)) { + PDFObject obj = (PDFObject) iter.next(); + if (compare.contentEquals(obj)) { return obj; } } diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 087ae4277..db2e99875 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -174,22 +174,24 @@ public class PDFFactory { * PDFDocument later using addObject(). * * @param resources resources object to use - * @param pageWidth width of the page in points - * @param pageHeight height of the page in points * @param pageIndex index of the page (zero-based) + * @param mediaBox the MediaBox area + * @param cropBox the CropBox area + * @param bleedBox the BleedBox area + * @param trimBox the TrimBox area * @param currentPageParentKey the integer key in the structural parent tree * * @return the created /Page object */ - public PDFPage makePage(PDFResources resources, - int pageWidth, int pageHeight, int pageIndex, + public PDFPage makePage(PDFResources resources, int pageIndex, + Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox, int currentPageParentKey) { /* * create a PDFPage with the next object number, the given * resources, contents and dimensions */ - PDFPage page = new PDFPage(resources, // old numPages - pageWidth, pageHeight, pageIndex); + PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); if (currentPageParentKey > -1) { //Accessibility is enabled page.setStructParents(currentPageParentKey); @@ -217,7 +219,8 @@ public class PDFFactory { */ public PDFPage makePage(PDFResources resources, int pageWidth, int pageHeight, int pageIndex) { - return makePage(resources, pageWidth, pageHeight, pageIndex, -1); + Rectangle2D mediaBox = new Rectangle2D.Double(0, 0, pageWidth, pageHeight); + return makePage(resources, pageIndex, mediaBox, mediaBox, mediaBox, mediaBox, -1); } /** diff --git a/src/java/org/apache/fop/pdf/PDFFileSpec.java b/src/java/org/apache/fop/pdf/PDFFileSpec.java index a2bc6aefe..8de4164af 100644 --- a/src/java/org/apache/fop/pdf/PDFFileSpec.java +++ b/src/java/org/apache/fop/pdf/PDFFileSpec.java @@ -63,13 +63,8 @@ public class PDFFileSpec extends PDFObject { * endobj */ - /** - * Check if this equals another object. - * - * @param obj the object to compare - * @return true if this equals other object - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (this == obj) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFFunction.java b/src/java/org/apache/fop/pdf/PDFFunction.java index 14cc318ba..d57cabb50 100644 --- a/src/java/org/apache/fop/pdf/PDFFunction.java +++ b/src/java/org/apache/fop/pdf/PDFFunction.java @@ -696,15 +696,8 @@ public class PDFFunction extends PDFObject { } - /** - * Check if this function is equal to another object. - * This is used to find if a particular function already exists - * in a document. - * - * @param obj the obj to compare - * @return true if the functions are equal - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (obj == null) { return false; } diff --git a/src/java/org/apache/fop/pdf/PDFGState.java b/src/java/org/apache/fop/pdf/PDFGState.java index 93151149b..4b997a145 100644 --- a/src/java/org/apache/fop/pdf/PDFGState.java +++ b/src/java/org/apache/fop/pdf/PDFGState.java @@ -175,10 +175,8 @@ public class PDFGState extends PDFObject { * endobj */ - /** - * {@inheritDoc} - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (obj == this) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java index b3ff6bcdc..ad04650c6 100644 --- a/src/java/org/apache/fop/pdf/PDFGoTo.java +++ b/src/java/org/apache/fop/pdf/PDFGoTo.java @@ -143,13 +143,8 @@ public class PDFGoTo extends PDFAction { * endobj */ - /** - * Check if this equals another object. - * - * @param obj the object to compare - * @return true if this equals other object - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (this == obj) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFGoToRemote.java b/src/java/org/apache/fop/pdf/PDFGoToRemote.java index e04a1668f..ee7660875 100644 --- a/src/java/org/apache/fop/pdf/PDFGoToRemote.java +++ b/src/java/org/apache/fop/pdf/PDFGoToRemote.java @@ -127,13 +127,8 @@ public class PDFGoToRemote extends PDFAction { * endobj */ - /** - * Check if this equals another object. - * - * @param obj the object to compare - * @return true if this equals other object - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (this == obj) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFLaunch.java b/src/java/org/apache/fop/pdf/PDFLaunch.java index d87fa2523..386a7a9a3 100644 --- a/src/java/org/apache/fop/pdf/PDFLaunch.java +++ b/src/java/org/apache/fop/pdf/PDFLaunch.java @@ -42,13 +42,8 @@ public class PDFLaunch extends PDFAction { return sb.toString(); } - /** - * Check if this equals another object. - * - * @param obj the object to compare - * @return true if this equals other object - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (this == obj) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java index 620e5d51d..66791e3ba 100644 --- a/src/java/org/apache/fop/pdf/PDFLink.java +++ b/src/java/org/apache/fop/pdf/PDFLink.java @@ -118,13 +118,8 @@ public class PDFLink extends PDFObject { * endobj */ - /** - * Check if this equals another object. - * - * @param obj the object to compare - * @return true if this equals other object - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (this == obj) { return true; } diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java index 97e9f4976..21ff82394 100644 --- a/src/java/org/apache/fop/pdf/PDFObject.java +++ b/src/java/org/apache/fop/pdf/PDFObject.java @@ -393,4 +393,20 @@ public abstract class PDFObject implements PDFWritable { return formatDateTime(time, TimeZone.getDefault()); } + /** + * Check if the other PDFObject has the same content as the current object. + * <p> + * Note: This function has a contract which is less binding than + * {@link #equals(Object)}. Whereas equals would require all values to be + * identical, this method is not required to check everything. In the case + * of PDFObjects, this means that the overriding function does not have to + * check for {@link #getObjectID()}. + * + * @param o + * object to compare to. + * @return true if the other object has the same content. + */ + protected boolean contentEquals(PDFObject o) { + return this.equals(o); + } } diff --git a/src/java/org/apache/fop/pdf/PDFPage.java b/src/java/org/apache/fop/pdf/PDFPage.java index 6cc8c3e57..1bcaa65c6 100644 --- a/src/java/org/apache/fop/pdf/PDFPage.java +++ b/src/java/org/apache/fop/pdf/PDFPage.java @@ -38,42 +38,42 @@ public class PDFPage extends PDFResourceContext { * Create a /Page object * * @param resources the /Resources object - * @param contents the content stream - * @param pageWidth the page's width in points - * @param pageHeight the page's height in points * @param pageIndex the page's zero-based index (or -1 if the page number is auto-determined) + * @param mediaBox the MediaBox + * @param cropBox the CropBox. If null, mediaBox is used. + * @param bleedBox the BleedBox. If null, cropBox is used. + * @param trimBox the TrimBox. If null, bleedBox is used. */ - public PDFPage(PDFResources resources, PDFStream contents, - int pageWidth, int pageHeight, int pageIndex) { + public PDFPage(PDFResources resources, int pageIndex, + Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { + /* generic creation of object */ + super(resources); + + put("Type", new PDFName("Page")); + /* set fields using parameters */ + setSimplePageSize(mediaBox, cropBox, bleedBox, trimBox); + this.pageIndex = pageIndex; + } - /* generic creation of object */ - super(resources); + private void setSimplePageSize(Rectangle2D mediaBox, Rectangle2D cropBox, + Rectangle2D bleedBox, Rectangle2D trimBox) { + setMediaBox(mediaBox); - put("Type", new PDFName("Page")); - /* set fields using parameters */ - setContents(contents); - setSimplePageSize(pageWidth, pageHeight); - this.pageIndex = pageIndex; - } + if (cropBox == null) { + cropBox = mediaBox; + } + setCropBox(cropBox); - /** - * Create a /Page object - * - * @param resources the /Resources object - * @param pageWidth the page's width in points - * @param pageHeight the page's height in points - * @param pageIndex the page's zero-based index (or -1 if the page number is auto-determined) - */ - public PDFPage(PDFResources resources, - int pageWidth, int pageHeight, int pageIndex) { - this(resources, null, pageWidth, pageHeight, pageIndex); - } + if (bleedBox == null) { + bleedBox = cropBox; + } + setBleedBox(bleedBox); //Recommended by PDF/X - private void setSimplePageSize(int width, int height) { - Rectangle2D box = new Rectangle2D.Double(0, 0, width, height); - setMediaBox(box); - setBleedBox(box); //Recommended by PDF/X - setTrimBox(box); //Needed for PDF/X + if (trimBox == null) { + trimBox = bleedBox; + } + setTrimBox(trimBox); //Needed for PDF/X } private PDFArray toPDFArray(Rectangle2D box) { @@ -90,11 +90,11 @@ public class PDFPage extends PDFResourceContext { } /** - * Sets the "TrimBox" entry - * @param box the trim rectangle + * Sets the "CropBox" entry + * @param box the bleed rectangle */ - public void setTrimBox(Rectangle2D box) { - put("TrimBox", toPDFArray(box)); + public void setCropBox(Rectangle2D box) { + put("CropBox", toPDFArray(box)); } /** @@ -106,6 +106,14 @@ public class PDFPage extends PDFResourceContext { } /** + * Sets the "TrimBox" entry + * @param box the trim rectangle + */ + public void setTrimBox(Rectangle2D box) { + put("TrimBox", toPDFArray(box)); + } + + /** * set this page contents * * @param contents the contents of the page diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java index 4e862c3f2..89ae1efcd 100644 --- a/src/java/org/apache/fop/pdf/PDFPattern.java +++ b/src/java/org/apache/fop/pdf/PDFPattern.java @@ -336,13 +336,8 @@ public class PDFPattern extends PDFPathPaint { */ public byte[] toPDF() { return null; } - /** - * Check if this pattern is equal to another. - * - * @param obj the object to compare against - * @return true if the patterns are equal - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (obj == null) { return false; } diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java index 5ac7245c9..fa4d454ed 100644 --- a/src/java/org/apache/fop/pdf/PDFShading.java +++ b/src/java/org/apache/fop/pdf/PDFShading.java @@ -529,14 +529,8 @@ public class PDFShading extends PDFObject { return (p.toString()); } - /** - * Check if this shading is equal to another shading. - * This is used to check if a shading already exists. - * - * @param obj the object to compare against - * @return true if the shadings are equal - */ - public boolean equals(Object obj) { + /** {@inheritDoc} */ + protected boolean contentEquals(PDFObject obj) { if (obj == null) { return false; } diff --git a/src/java/org/apache/fop/render/AbstractConfigurator.java b/src/java/org/apache/fop/render/AbstractConfigurator.java index b1ac1c61a..096007b98 100644 --- a/src/java/org/apache/fop/render/AbstractConfigurator.java +++ b/src/java/org/apache/fop/render/AbstractConfigurator.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.render; diff --git a/src/java/org/apache/fop/render/DummyPercentBaseContext.java b/src/java/org/apache/fop/render/DummyPercentBaseContext.java new file mode 100644 index 000000000..f9ecf63fe --- /dev/null +++ b/src/java/org/apache/fop/render/DummyPercentBaseContext.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import org.apache.fop.datatypes.PercentBaseContext; +import org.apache.fop.fo.FObj; + +/** + * A dummy implementation of PercentBaseContext + */ +public final class DummyPercentBaseContext implements PercentBaseContext { + + private static DummyPercentBaseContext singleton = new DummyPercentBaseContext(); + + private DummyPercentBaseContext() { + } + + /** + * Returns an instance of this dummy implementation + * @return an instance of this dummy implementation + */ + public static DummyPercentBaseContext getInstance() { + return singleton; + } + + /** {@inheritDoc} */ + public int getBaseLength(int lengthBase, FObj fo) { + return 0; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/afp/AFPCustomizable.java b/src/java/org/apache/fop/render/afp/AFPCustomizable.java index ff4fb0100..ed1ea443b 100644 --- a/src/java/org/apache/fop/render/afp/AFPCustomizable.java +++ b/src/java/org/apache/fop/render/afp/AFPCustomizable.java @@ -51,6 +51,12 @@ public interface AFPCustomizable { void setNativeImagesSupported(boolean nativeImages); /** + * Sets the shading mode for painting filled rectangles. + * @param shadingMode the shading mode + */ + void setShadingMode(AFPShadingMode shadingMode); + + /** * Sets the output/device resolution * * @param resolution diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java index 8d29145cf..073d43e20 100644 --- a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -25,19 +25,26 @@ import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.Map; +import org.apache.fop.afp.AFPDitheredRectanglePainter; import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPRectanglePainter; import org.apache.fop.afp.AFPResourceLevelDefaults; import org.apache.fop.afp.AFPResourceManager; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.fonts.AFPFontCollection; import org.apache.fop.afp.fonts.AFPPageFonts; +import org.apache.fop.afp.modca.ResourceObject; +import org.apache.fop.afp.util.DefaultFOPResourceAccessor; +import org.apache.fop.afp.util.ResourceAccessor; import org.apache.fop.apps.MimeConstants; import org.apache.fop.fonts.FontCollection; import org.apache.fop.fonts.FontEventAdapter; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontManager; import org.apache.fop.render.afp.extensions.AFPElementMapping; +import org.apache.fop.render.afp.extensions.AFPIncludeFormMap; import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap; import org.apache.fop.render.afp.extensions.AFPPageSetup; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; @@ -70,12 +77,18 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler private Map/*<String,String>*/pageSegmentMap = new java.util.HashMap/*<String,String>*/(); + /** Medium Map referenced on previous page **/ + private String lastMediumMap; + private static final int LOC_ELSEWHERE = 0; private static final int LOC_FOLLOWING_PAGE_SEQUENCE = 1; private static final int LOC_IN_PAGE_HEADER = 2; private int location = LOC_ELSEWHERE; + /** the shading mode for filled rectangles */ + private AFPShadingMode shadingMode = AFPShadingMode.COLOR; + /** * Default constructor. */ @@ -125,6 +138,16 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler return this.resourceManager; } + AbstractAFPPainter createRectanglePainter() { + if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { + return new AFPDitheredRectanglePainter( + getPaintingState(), getDataStream(), getResourceManager()); + } else { + return new AFPRectanglePainter( + getPaintingState(), getDataStream()); + } + } + /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); @@ -259,7 +282,8 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } else { if (this.location != LOC_IN_PAGE_HEADER) { throw new IFException( - "AFP page setup extension encountered outside the page header: " + aps, null); + "AFP page setup extension encountered outside the page header: " + aps, + null); } if (AFPElementMapping.INCLUDE_PAGE_OVERLAY.equals(element)) { String overlay = aps.getName(); @@ -278,15 +302,31 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } } } else if (extension instanceof AFPInvokeMediumMap) { - if (this.location != LOC_FOLLOWING_PAGE_SEQUENCE) { + if (this.location != LOC_FOLLOWING_PAGE_SEQUENCE + && this.location != LOC_IN_PAGE_HEADER) { + throw new IFException( - "AFP IMM extension must be between page-sequence and the first page: " - + extension, null); + "AFP IMM extension must be between page-sequence" + + " and the first page or child of page-header: " + + extension, null); } AFPInvokeMediumMap imm = (AFPInvokeMediumMap)extension; String mediumMap = imm.getName(); - if (mediumMap != null) { + if (mediumMap != null && !mediumMap.equals(lastMediumMap)) { dataStream.createInvokeMediumMap(mediumMap); + lastMediumMap = mediumMap; + } + } else if (extension instanceof AFPIncludeFormMap) { + AFPIncludeFormMap formMap = (AFPIncludeFormMap)extension; + ResourceAccessor accessor = new DefaultFOPResourceAccessor( + getUserAgent(), null, null); + try { + getResourceManager().createIncludedResource(formMap.getName(), + formMap.getSrc(), accessor, + ResourceObject.TYPE_FORMDEF); + } catch (IOException ioe) { + throw new IFException( + "I/O error while embedding form map resource: " + formMap.getName(), ioe); } } } @@ -309,6 +349,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } /** {@inheritDoc} */ + public void setShadingMode(AFPShadingMode shadingMode) { + this.shadingMode = shadingMode; + } + + /** {@inheritDoc} */ public void setResolution(int resolution) { paintingState.setResolution(resolution); } diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java index 440db3eda..cbe6d0ca3 100644 --- a/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java @@ -30,8 +30,10 @@ import org.apache.fop.render.intermediate.IFDocumentHandler; */ public class AFPDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker { - private static final String[] MIMES = new String[] - {MimeConstants.MIME_AFP}; + private static final String[] MIMES = new String[] { + MimeConstants.MIME_AFP, + MimeConstants.MIME_AFP_ALT + }; /** {@inheritDoc} */ public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) { diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index a92be9d6e..7fcd0b3d1 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -35,8 +35,8 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPPaintingState; -import org.apache.fop.afp.AFPRectanglePainter; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.RectanglePaintingInfo; @@ -46,10 +46,8 @@ import org.apache.fop.afp.fonts.AFPPageFonts; import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.AbstractPageObject; import org.apache.fop.afp.modca.PresentationTextObject; -import org.apache.fop.afp.modca.ResourceObject; import org.apache.fop.afp.ptoca.PtocaBuilder; import org.apache.fop.afp.ptoca.PtocaProducer; -import org.apache.fop.afp.util.ResourceAccessor; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; @@ -79,7 +77,7 @@ public class AFPPainter extends AbstractIFPainter { /** the border painter */ private AFPBorderPainterAdapter borderPainter; /** the rectangle painter */ - private AFPRectanglePainter rectanglePainter; + private AbstractAFPPainter rectanglePainter; /** unit converter */ private final AFPUnitConverter unitConv; @@ -94,7 +92,7 @@ public class AFPPainter extends AbstractIFPainter { this.state = IFState.create(); this.borderPainter = new AFPBorderPainterAdapter( new AFPBorderPainter(getPaintingState(), getDataStream())); - this.rectanglePainter = new AFPRectanglePainter(getPaintingState(), getDataStream()); + this.rectanglePainter = documentHandler.createRectanglePainter(); this.unitConv = getPaintingState().getUnitConverter(); } @@ -222,7 +220,11 @@ public class AFPPainter extends AbstractIFPainter { } RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo( toPoint(rect.x), toPoint(rect.y), toPoint(rect.width), toPoint(rect.height)); - rectanglePainter.paint(rectanglePaintInfo); + try { + rectanglePainter.paint(rectanglePaintInfo); + } catch (IOException ioe) { + throw new IFException("IO error while painting rectangle", ioe); + } } } @@ -341,17 +343,7 @@ public class AFPPainter extends AbstractIFPainter { if (afpFont.isEmbeddable()) { try { - //Embed fonts (char sets and code pages) - //TODO This should be moved to a place where it has less performance impact - if (charSet.getResourceAccessor() != null) { - ResourceAccessor accessor = charSet.getResourceAccessor(); - documentHandler.getResourceManager().createIncludedResource( - charSet.getName(), accessor, - ResourceObject.TYPE_FONT_CHARACTER_SET); - documentHandler.getResourceManager().createIncludedResource( - charSet.getCodePage(), accessor, - ResourceObject.TYPE_CODE_PAGE); - } + documentHandler.getResourceManager().embedFont(afpFont, charSet); } catch (IOException ioe) { throw new IFException("Error while embedding font resources", ioe); } diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index 94fd05dc8..e106ac712 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -43,6 +43,7 @@ import org.apache.xmlgraphics.ps.ImageEncodingHelper; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.fop.afp.AFPDitheredRectanglePainter; import org.apache.fop.afp.AFPEventProducer; import org.apache.fop.afp.AFPPaintingState; import org.apache.fop.afp.AFPRectanglePainter; @@ -50,6 +51,7 @@ import org.apache.fop.afp.AFPResourceLevelDefaults; import org.apache.fop.afp.AFPResourceManager; import org.apache.fop.afp.AFPTextDataInfo; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.RectanglePaintingInfo; @@ -59,10 +61,14 @@ import org.apache.fop.afp.fonts.AFPFontCollection; import org.apache.fop.afp.fonts.AFPPageFonts; import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.PageObject; +import org.apache.fop.afp.modca.ResourceObject; +import org.apache.fop.afp.util.DefaultFOPResourceAccessor; +import org.apache.fop.afp.util.ResourceAccessor; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.CTM; +import org.apache.fop.area.OffDocumentExtensionAttachment; import org.apache.fop.area.OffDocumentItem; import org.apache.fop.area.PageSequence; import org.apache.fop.area.PageViewport; @@ -73,6 +79,7 @@ import org.apache.fop.area.inline.TextArea; import org.apache.fop.datatypes.URISpecification; import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontCollection; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontManager; @@ -80,6 +87,8 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; import org.apache.fop.render.afp.extensions.AFPElementMapping; +import org.apache.fop.render.afp.extensions.AFPExtensionAttachment; +import org.apache.fop.render.afp.extensions.AFPIncludeFormMap; import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap; import org.apache.fop.render.afp.extensions.AFPPageSetup; @@ -167,7 +176,13 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** the image handler registry */ private final AFPImageHandlerRegistry imageHandlerRegistry; - private AFPRectanglePainter rectanglePainter; + private AbstractAFPPainter rectanglePainter; + + /** the shading mode for filled rectangles */ + private AFPShadingMode shadingMode = AFPShadingMode.COLOR; + + /** medium map referenced used on previous page **/ + private String lastMediumMap; /** * Constructor for AFPRenderer. @@ -201,11 +216,21 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust this.dataStream = resourceManager.createDataStream(paintingState, outputStream); this.borderPainter = new AFPBorderPainter(paintingState, dataStream); - this.rectanglePainter = new AFPRectanglePainter(paintingState, dataStream); + this.rectanglePainter = createRectanglePainter(); dataStream.startDocument(); } + AbstractAFPPainter createRectanglePainter() { + if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { + return new AFPDitheredRectanglePainter( + this.paintingState, this.dataStream, this.resourceManager); + } else { + return new AFPRectanglePainter( + this.paintingState, this.dataStream); + } + } + /** {@inheritDoc} */ public void stopRenderer() throws IOException { dataStream.endDocument(); @@ -263,8 +288,30 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** {@inheritDoc} */ public void processOffDocumentItem(OffDocumentItem odi) { - // TODO - log.debug("NYI processOffDocumentItem(" + odi + ")"); + if (odi instanceof OffDocumentExtensionAttachment) { + ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); + if (attachment != null) { + if (AFPExtensionAttachment.CATEGORY.equals(attachment.getCategory())) { + if (attachment instanceof AFPIncludeFormMap) { + handleIncludeFormMap((AFPIncludeFormMap)attachment); + } + } + } + } + } + + private void handleIncludeFormMap(AFPIncludeFormMap formMap) { + ResourceAccessor accessor = new DefaultFOPResourceAccessor( + getUserAgent(), null, null); + try { + this.resourceManager.createIncludedResource(formMap.getName(), + formMap.getSrc(), accessor, + ResourceObject.TYPE_FORMDEF); + } catch (IOException ioe) { + AFPEventProducer eventProducer + = AFPEventProducer.Provider.get(userAgent.getEventBroadcaster()); + eventProducer.resourceEmbeddingError(this, formMap.getName(), ioe); + } } /** {@inheritDoc} */ @@ -336,6 +383,9 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust int resolution = paintingState.getResolution(); + // IMM should occur before BPG + renderInvokeMediumMap(pageViewport); + dataStream.startPage(pageWidth, pageHeight, pageRotation, resolution, resolution); @@ -362,7 +412,12 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** {@inheritDoc} */ public void fillRect(float x, float y, float width, float height) { RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo(x, y, width, height); - rectanglePainter.paint(rectanglePaintInfo); + try { + rectanglePainter.paint(rectanglePaintInfo); + } catch (IOException ioe) { + //TODO not ideal, but the AFPRenderer is legacy + throw new RuntimeException("I/O error while painting a filled rectangle", ioe); + } } /** {@inheritDoc} */ @@ -543,6 +598,18 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust AFPFont font = (AFPFont)fontMetricMap.get(internalFontName); AFPPageFonts pageFonts = paintingState.getPageFonts(); AFPFontAttributes fontAttributes = pageFonts.registerFont(internalFontName, font, fontSize); + Font fnt = getFontFromArea(text); + + if (font.isEmbeddable()) { + CharacterSet charSet = font.getCharacterSet(fontSize); + try { + this.resourceManager.embedFont(font, charSet); + } catch (IOException ioe) { + AFPEventProducer eventProducer + = AFPEventProducer.Provider.get(userAgent.getEventBroadcaster()); + eventProducer.resourceEmbeddingError(this, charSet.getName(), ioe); + } + } // create text data info AFPTextDataInfo textDataInfo = new AFPTextDataInfo(); @@ -583,7 +650,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust textDataInfo.setString(textString); try { - dataStream.createText(textDataInfo); + dataStream.createText(textDataInfo, textLetterSpaceAdjust, textWordSpaceAdjust, fnt, charSet); } catch (UnsupportedEncodingException e) { AFPEventProducer eventProducer = AFPEventProducer.Provider.get(userAgent.getEventBroadcaster()); @@ -643,6 +710,35 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust } /** + * checks for IMM Extension and renders if found and different + * from previous page + * + * @param pageViewport the page object + */ + private void renderInvokeMediumMap(PageViewport pageViewport) { + if (pageViewport.getExtensionAttachments() != null + && pageViewport.getExtensionAttachments().size() > 0) { + Iterator it = pageViewport.getExtensionAttachments().iterator(); + while (it.hasNext()) { + ExtensionAttachment attachment = (ExtensionAttachment) it.next(); + if (AFPExtensionAttachment.CATEGORY.equals(attachment.getCategory())) { + AFPExtensionAttachment aea = (AFPExtensionAttachment)attachment; + if (AFPElementMapping.INVOKE_MEDIUM_MAP.equals(aea.getElementName())) { + AFPInvokeMediumMap imm = (AFPInvokeMediumMap)attachment; + String mediumMap = imm.getName(); + if (mediumMap != null) { + if (!mediumMap.equals(lastMediumMap)) { + dataStream.createInvokeMediumMap(mediumMap); + lastMediumMap = mediumMap; + } + } + } + } + } + } + } + + /** * Method to render the page extension. * <p> * @@ -659,27 +755,29 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust while (it.hasNext()) { ExtensionAttachment attachment = (ExtensionAttachment) it.next(); if (AFPPageSetup.CATEGORY.equals(attachment.getCategory())) { - AFPPageSetup aps = (AFPPageSetup) attachment; - String element = aps.getElementName(); - if (AFPElementMapping.INCLUDE_PAGE_OVERLAY.equals(element)) { - String overlay = aps.getName(); - if (overlay != null) { - dataStream.createIncludePageOverlay(overlay); - } - } else if (AFPElementMapping.INCLUDE_PAGE_SEGMENT - .equals(element)) { - String name = aps.getName(); - String source = aps.getValue(); - pageSegmentMap.put(source, name); - } else if (AFPElementMapping.TAG_LOGICAL_ELEMENT - .equals(element)) { - String name = aps.getName(); - String value = aps.getValue(); - dataStream.createTagLogicalElement(name, value); - } else if (AFPElementMapping.NO_OPERATION.equals(element)) { - String content = aps.getContent(); - if (content != null) { - dataStream.createNoOperation(content); + if (attachment instanceof AFPPageSetup) { + AFPPageSetup aps = (AFPPageSetup) attachment; + String element = aps.getElementName(); + if (AFPElementMapping.INCLUDE_PAGE_OVERLAY.equals(element)) { + String overlay = aps.getName(); + if (overlay != null) { + dataStream.createIncludePageOverlay(overlay); + } + } else if (AFPElementMapping.INCLUDE_PAGE_SEGMENT + .equals(element)) { + String name = aps.getName(); + String source = aps.getValue(); + pageSegmentMap.put(source, name); + } else if (AFPElementMapping.TAG_LOGICAL_ELEMENT + .equals(element)) { + String name = aps.getName(); + String value = aps.getValue(); + dataStream.createTagLogicalElement(name, value); + } else if (AFPElementMapping.NO_OPERATION.equals(element)) { + String content = aps.getContent(); + if (content != null) { + dataStream.createNoOperation(content); + } } } } @@ -728,6 +826,11 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust } /** {@inheritDoc} */ + public void setShadingMode(AFPShadingMode shadingMode) { + this.shadingMode = shadingMode; + } + + /** {@inheritDoc} */ public void setResolution(int resolution) { paintingState.setResolution(resolution); } diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java index fcc1140c7..007dd3861 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java @@ -20,6 +20,7 @@ package org.apache.fop.render.afp; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; @@ -67,8 +68,12 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator } private AFPFontInfo buildFont(Configuration fontCfg, String fontPath) - throws ConfigurationException { + throws ConfigurationException { + + FontManager fontManager = this.userAgent.getFactory().getFontManager(); + FontTriplet.Matcher referencedFontsMatcher = fontManager.getReferencedFontsMatcher(); + boolean embeddable = true; Configuration[] triple = fontCfg.getChildren("font-triplet"); List/*<FontTriplet>*/ tripletList = new java.util.ArrayList/*<FontTriplet>*/(); if (triple.length == 0) { @@ -80,6 +85,9 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator FontTriplet triplet = new FontTriplet(triple[j].getAttribute("name"), triple[j].getAttribute("style"), weight); + if (referencedFontsMatcher != null && referencedFontsMatcher.matches(triplet)) { + embeddable = false; + } tripletList.add(triplet); } @@ -109,7 +117,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator } ResourceAccessor accessor = new DefaultFOPResourceAccessor( this.userAgent, - this.userAgent.getFactory().getFontManager().getFontBaseURL(), + fontManager.getFontBaseURL(), baseURI); String type = afpFontCfg.getAttribute("type"); @@ -134,11 +142,12 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator // Create a new font object RasterFont font = new RasterFont(name); + font.setEmbeddable(embeddable); Configuration[] rasters = afpFontCfg.getChildren("afp-raster-font"); if (rasters.length == 0) { - log.error( - "Mandatory font configuration elements '<afp-raster-font...' are missing"); + log.error("Mandatory font configuration elements '<afp-raster-font...'" + + " are missing at " + afpFontCfg.getLocation()); return null; } for (int j = 0; j < rasters.length; j++) { @@ -150,7 +159,8 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator "Mandatory afp-raster-font configuration attribute 'characterset=' is missing"); return null; } - int size = rasterCfg.getAttributeAsInteger("size"); + float size = rasterCfg.getAttributeAsFloat("size"); + int sizeMpt = (int)(size * 1000); String base14 = rasterCfg.getAttribute("base14-font", null); if (base14 != null) { @@ -159,7 +169,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator + base14); try { Typeface tf = (Typeface)clazz.newInstance(); - font.addCharacterSet(size, new FopCharacterSet( + font.addCharacterSet(sizeMpt, new FopCharacterSet( codepage, encoding, characterset, tf)); } catch (Exception ie) { String msg = "The base 14 font class " + clazz.getName() @@ -172,7 +182,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator log.error(msg); } } else { - font.addCharacterSet(size, new CharacterSet( + font.addCharacterSet(sizeMpt, new CharacterSet( codepage, encoding, characterset, accessor)); } } @@ -210,6 +220,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator } // Create a new font object OutlineFont font = new OutlineFont(name, characterSet); + font.setEmbeddable(embeddable); return new AFPFontInfo(font, tripletList); } else { log.error("No or incorrect type attribute"); @@ -299,6 +310,12 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false); customizable.setNativeImagesSupported(nativeImageSupport); + // shading (filled rectangles) + Configuration shadingCfg = cfg.getChild("shading"); + AFPShadingMode shadingMode = AFPShadingMode.valueOf( + shadingCfg.getValue(AFPShadingMode.COLOR.getName())); + customizable.setShadingMode(shadingMode); + // renderer resolution Configuration rendererResolutionCfg = cfg.getChild("renderer-resolution", false); if (rendererResolutionCfg != null) { @@ -312,16 +329,21 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator String resourceGroupDest = null; try { resourceGroupDest = resourceGroupFileCfg.getValue(); + if (resourceGroupDest != null) { + File resourceGroupFile = new File(resourceGroupDest); + resourceGroupFile.createNewFile(); + if (resourceGroupFile.canWrite()) { + customizable.setDefaultResourceGroupFilePath(resourceGroupDest); + } else { + log.warn("Unable to write to default external resource group file '" + + resourceGroupDest + "'"); + } + } } catch (ConfigurationException e) { LogUtil.handleException(log, e, userAgent.getFactory().validateUserConfigStrictly()); - } - File resourceGroupFile = new File(resourceGroupDest); - if (resourceGroupFile.canWrite()) { - customizable.setDefaultResourceGroupFilePath(resourceGroupDest); - } else { - log.warn("Unable to write to default external resource group file '" - + resourceGroupDest + "'"); + } catch (IOException ioe) { + throw new FOPException("Could not create default external resource group file", ioe); } } diff --git a/src/java/org/apache/fop/render/afp/AFPShadingMode.java b/src/java/org/apache/fop/render/afp/AFPShadingMode.java new file mode 100644 index 000000000..b45c33a8e --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPShadingMode.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.afp; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** Enumeration class for the AFP shading mode. */ +public final class AFPShadingMode implements Serializable { + + private static final long serialVersionUID = 8579867898716480779L; + + /** the color mode (the default) */ + public static final AFPShadingMode COLOR = new AFPShadingMode("COLOR"); + /** the dithered mode */ + public static final AFPShadingMode DITHERED = new AFPShadingMode("DITHERED"); + + private String name; + + /** + * Constructor to add a new named item. + * @param name Name of the item. + */ + private AFPShadingMode(String name) { + this.name = name; + } + + /** @return the name of the enumeration */ + public String getName() { + return this.name; + } + + /** + * Returns the enumeration/singleton object based on its name. + * @param name the name of the enumeration value + * @return the enumeration object + */ + public static AFPShadingMode valueOf(String name) { + if (COLOR.getName().equalsIgnoreCase(name)) { + return COLOR; + } else if (DITHERED.getName().equalsIgnoreCase(name)) { + return DITHERED; + } else { + throw new IllegalArgumentException("Illegal value for enumeration: " + name); + } + } + + private Object readResolve() throws ObjectStreamException { + return valueOf(getName()); + } + + /** {@inheritDoc} */ + public String toString() { + return getClass().getName() + ":" + name; + } + +} diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPElementMapping.java b/src/java/org/apache/fop/render/afp/extensions/AFPElementMapping.java index 83615b75d..cb5478340 100755 --- a/src/java/org/apache/fop/render/afp/extensions/AFPElementMapping.java +++ b/src/java/org/apache/fop/render/afp/extensions/AFPElementMapping.java @@ -42,6 +42,9 @@ public class AFPElementMapping extends ElementMapping { /** include page segment element */ public static final String INCLUDE_PAGE_SEGMENT = "include-page-segment"; + /** include form map element */ + public static final String INCLUDE_FORM_MAP = "include-form-map"; + /** NOP */ public static final String NO_OPERATION = "no-operation"; @@ -81,6 +84,9 @@ public class AFPElementMapping extends ElementMapping { INCLUDE_PAGE_OVERLAY, new AFPIncludePageOverlayMaker()); foObjs.put( + INCLUDE_FORM_MAP, + new AFPIncludeFormMapMaker()); + foObjs.put( NO_OPERATION, new AFPNoOperationMaker()); foObjs.put( @@ -101,6 +107,12 @@ public class AFPElementMapping extends ElementMapping { } } + static class AFPIncludeFormMapMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new AFPIncludeFormMapElement(parent, INCLUDE_FORM_MAP); + } + } + static class AFPTagLogicalElementMaker extends ElementMapping.Maker { public FONode make(FONode parent) { return new AFPPageSetupElement(parent, TAG_LOGICAL_ELEMENT); diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPExtensionAttachment.java b/src/java/org/apache/fop/render/afp/extensions/AFPExtensionAttachment.java index e027e7f32..cc8de5f84 100644 --- a/src/java/org/apache/fop/render/afp/extensions/AFPExtensionAttachment.java +++ b/src/java/org/apache/fop/render/afp/extensions/AFPExtensionAttachment.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.render.afp.extensions; diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPExtensionHandler.java b/src/java/org/apache/fop/render/afp/extensions/AFPExtensionHandler.java index 1a8cfdf47..c487b1825 100644 --- a/src/java/org/apache/fop/render/afp/extensions/AFPExtensionHandler.java +++ b/src/java/org/apache/fop/render/afp/extensions/AFPExtensionHandler.java @@ -19,6 +19,9 @@ package org.apache.fop.render.afp.extensions; +import java.net.URI; +import java.net.URISyntaxException; + import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -48,13 +51,14 @@ public class AFPExtensionHandler extends DefaultHandler public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { boolean handled = false; - if (AFPPageSetup.CATEGORY.equals(uri)) { + if (AFPExtensionAttachment.CATEGORY.equals(uri)) { lastAttributes = attributes; handled = true; if (localName.equals(AFPElementMapping.NO_OPERATION) || localName.equals(AFPElementMapping.TAG_LOGICAL_ELEMENT) || localName.equals(AFPElementMapping.INCLUDE_PAGE_OVERLAY) || localName.equals(AFPElementMapping.INCLUDE_PAGE_SEGMENT) + || localName.equals(AFPElementMapping.INCLUDE_FORM_MAP) || localName.equals(AFPElementMapping.INVOKE_MEDIUM_MAP)) { //handled in endElement } else { @@ -62,7 +66,7 @@ public class AFPExtensionHandler extends DefaultHandler } } if (!handled) { - if (AFPPageSetup.CATEGORY.equals(uri)) { + if (AFPExtensionAttachment.CATEGORY.equals(uri)) { throw new SAXException("Unhandled element " + localName + " in namespace: " + uri); } else { @@ -74,26 +78,38 @@ public class AFPExtensionHandler extends DefaultHandler /** {@inheritDoc} */ public void endElement(String uri, String localName, String qName) throws SAXException { - if (AFPPageSetup.CATEGORY.equals(uri)) { - AFPPageSetup pageSetupExtn = null; - if (localName.equals(AFPElementMapping.INVOKE_MEDIUM_MAP)) { - this.returnedObject = new AFPInvokeMediumMap(); - } - else { - pageSetupExtn = new AFPPageSetup(localName); - this.returnedObject = pageSetupExtn; - } - String name = lastAttributes.getValue("name"); - if (name != null) { - returnedObject.setName(name); - } - String value = lastAttributes.getValue("value"); - if (value != null && pageSetupExtn != null) { - pageSetupExtn.setValue(value); - } - if (content.length() > 0 && pageSetupExtn != null) { - pageSetupExtn.setContent(content.toString()); - content.setLength(0); //Reset text buffer (see characters()) + if (AFPExtensionAttachment.CATEGORY.equals(uri)) { + if (AFPElementMapping.INCLUDE_FORM_MAP.equals(localName)) { + AFPIncludeFormMap formMap = new AFPIncludeFormMap(); + String name = lastAttributes.getValue("name"); + formMap.setName(name); + String src = lastAttributes.getValue("src"); + try { + formMap.setSrc(new URI(src)); + } catch (URISyntaxException e) { + throw new SAXException("Invalid URI: " + src, e); + } + this.returnedObject = formMap; + } else { + AFPPageSetup pageSetupExtn = null; + if (AFPElementMapping.INVOKE_MEDIUM_MAP.equals(localName)) { + this.returnedObject = new AFPInvokeMediumMap(); + } else { + pageSetupExtn = new AFPPageSetup(localName); + this.returnedObject = pageSetupExtn; + } + String name = lastAttributes.getValue("name"); + if (name != null) { + returnedObject.setName(name); + } + String value = lastAttributes.getValue("value"); + if (value != null && pageSetupExtn != null) { + pageSetupExtn.setValue(value); + } + if (content.length() > 0 && pageSetupExtn != null) { + pageSetupExtn.setContent(content.toString()); + content.setLength(0); //Reset text buffer (see characters()) + } } } } diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMap.java b/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMap.java new file mode 100644 index 000000000..06c7cbc9d --- /dev/null +++ b/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMap.java @@ -0,0 +1,87 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.afp.extensions; + +import java.net.URI; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.fop.fo.extensions.ExtensionAttachment; + +/** + * This extension allows to include an AFP form map resource. It is implemented as an extension + * attachment ({@link ExtensionAttachment}). + */ +public class AFPIncludeFormMap extends AFPExtensionAttachment { + + private static final long serialVersionUID = 8548056652642588914L; + + /** src attribute containing the URI to the form map resource */ + protected static final String ATT_SRC = "src"; + + /** + * the URI identifying the form map resource. + */ + protected URI src; + + /** + * Default constructor. + */ + public AFPIncludeFormMap() { + super(AFPElementMapping.INCLUDE_FORM_MAP); + } + + /** + * Returns the URI of the form map. + * @return the form map URI + */ + public URI getSrc() { + return this.src; + } + + /** + * Sets the URI of the form map. + * @param value the form map URI + */ + public void setSrc(URI value) { + this.src = value; + } + + /** {@inheritDoc} */ + public void toSAX(ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (name != null && name.length() > 0) { + atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name); + } + if (this.src != null) { + atts.addAttribute(null, ATT_SRC, ATT_SRC, "CDATA", this.src.toASCIIString()); + } + handler.startElement(CATEGORY, elementName, elementName, atts); + handler.endElement(CATEGORY, elementName, elementName); + } + + /** {@inheritDoc} */ + public String toString() { + return getClass().getName() + "(element-name=" + getElementName() + + " name=" + getName() + " src=" + getSrc() + ")"; + } +} diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMapElement.java b/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMapElement.java new file mode 100644 index 000000000..719d8c765 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/extensions/AFPIncludeFormMapElement.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.afp.extensions; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.fo.extensions.ExtensionObj; + +/** + * This class extends the {@link ExtensionObj} class. It represents the "include-form-map" + * extension in the FO tree. + */ +public class AFPIncludeFormMapElement extends AbstractAFPExtensionObject { + + private static final String ATT_SRC = "src"; + + /** + * Constructs an AFP object (called by Maker). + * + * @param parent the parent formatting object + * @param name the name of the AFP element + */ + public AFPIncludeFormMapElement(FONode parent, String name) { + super(parent, name); + } + + private AFPIncludeFormMap getFormMapAttachment() { + return (AFPIncludeFormMap)getExtensionAttachment(); + } + + /** {@inheritDoc} */ + protected void startOfNode() throws FOPException { + super.startOfNode(); + if (parent.getNameId() != Constants.FO_DECLARATIONS) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), + "rule.childOfDeclarations"); + } + } + + /** {@inheritDoc} */ + public void processNode(String elementName, Locator locator, + Attributes attlist, PropertyList propertyList) + throws FOPException { + super.processNode(elementName, locator, attlist, propertyList); + AFPIncludeFormMap formMap = getFormMapAttachment(); + String attr = attlist.getValue(ATT_SRC); + if (attr != null && attr.length() > 0) { + try { + formMap.setSrc(new URI(attr)); + } catch (URISyntaxException e) { + getFOValidationEventProducer().invalidPropertyValue(this, + elementName, ATT_SRC, attr, null, getLocator()); + } + } else { + missingPropertyError(ATT_SRC); + } + } + + /** {@inheritDoc} */ + protected ExtensionAttachment instantiateExtensionAttachment() { + return new AFPIncludeFormMap(); + } +} diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPInvokeMediumMapElement.java b/src/java/org/apache/fop/render/afp/extensions/AFPInvokeMediumMapElement.java index 99805edd4..f36bd3e12 100644 --- a/src/java/org/apache/fop/render/afp/extensions/AFPInvokeMediumMapElement.java +++ b/src/java/org/apache/fop/render/afp/extensions/AFPInvokeMediumMapElement.java @@ -26,8 +26,8 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; /** * This class represents an AFP-specific extension element to embed Invoke Medium Map (IMM) - * fields at the beginning of a page group. The element is optional and expected as a direct child - * of an fo:page-sequence. + * fields at the beginning of a page group or just prior to a Page. The element is optional + * and expected as a direct child of an fo:page-sequence or fo:simple-page-master */ public class AFPInvokeMediumMapElement extends AbstractAFPExtensionObject { @@ -42,7 +42,9 @@ public class AFPInvokeMediumMapElement extends AbstractAFPExtensionObject { /** {@inheritDoc} */ protected void startOfNode() throws FOPException { super.startOfNode(); - if (parent.getNameId() != Constants.FO_PAGE_SEQUENCE) { + if (parent.getNameId() != Constants.FO_PAGE_SEQUENCE + && parent.getNameId() != Constants.FO_SIMPLE_PAGE_MASTER) { + invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(), "rule.childOfPageSequence"); } diff --git a/src/java/org/apache/fop/render/afp/extensions/AFPPageSetupElement.java b/src/java/org/apache/fop/render/afp/extensions/AFPPageSetupElement.java index 0c6dfadc4..19f98f32a 100755 --- a/src/java/org/apache/fop/render/afp/extensions/AFPPageSetupElement.java +++ b/src/java/org/apache/fop/render/afp/extensions/AFPPageSetupElement.java @@ -36,6 +36,9 @@ import org.apache.fop.fo.extensions.ExtensionAttachment; */ public class AFPPageSetupElement extends AbstractAFPExtensionObject { + private static final String ATT_VALUE = "value"; + private static final String ATT_SRC = "src"; + /** * Constructs an AFP object (called by Maker). * @@ -86,18 +89,18 @@ public class AFPPageSetupElement extends AbstractAFPExtensionObject { super.processNode(elementName, locator, attlist, propertyList); AFPPageSetup pageSetup = getPageSetupAttachment(); if (AFPElementMapping.INCLUDE_PAGE_SEGMENT.equals(elementName)) { - String attr = attlist.getValue("src"); + String attr = attlist.getValue(ATT_SRC); if (attr != null && attr.length() > 0) { pageSetup.setValue(attr); } else { - throw new FOPException(elementName + " must have a src attribute."); + missingPropertyError(ATT_SRC); } } else if (AFPElementMapping.TAG_LOGICAL_ELEMENT.equals(elementName)) { - String attr = attlist.getValue("value"); + String attr = attlist.getValue(ATT_VALUE); if (attr != null && attr.length() > 0) { pageSetup.setValue(attr); } else { - throw new FOPException(elementName + " must have a value attribute."); + missingPropertyError(ATT_VALUE); } } } diff --git a/src/java/org/apache/fop/render/awt/AWTRenderer.java b/src/java/org/apache/fop/render/awt/AWTRenderer.java index 5b4c6b13a..b50708112 100644 --- a/src/java/org/apache/fop/render/awt/AWTRenderer.java +++ b/src/java/org/apache/fop/render/awt/AWTRenderer.java @@ -30,6 +30,7 @@ package org.apache.fop.render.awt; import java.awt.Color; import java.awt.Dimension; import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; import java.awt.print.PageFormat; import java.awt.print.Pageable; import java.awt.print.Paper; @@ -46,6 +47,7 @@ import org.apache.fop.render.awt.viewer.PreviewDialog; import org.apache.fop.render.awt.viewer.Renderable; import org.apache.fop.render.awt.viewer.StatusListener; import org.apache.fop.render.java2d.Java2DRenderer; +import org.apache.fop.render.extensions.prepress.PageScale; /** * The AWTRender outputs the pages generated by the layout engine to a Swing @@ -149,11 +151,23 @@ public class AWTRenderer extends Java2DRenderer implements Pageable { Rectangle2D bounds = getPageViewport(pageNum).getViewArea(); pageWidth = (int) Math.round(bounds.getWidth() / 1000f); pageHeight = (int) Math.round(bounds.getHeight() / 1000f); - double scale = scaleFactor + double scaleX = scaleFactor * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); - int bitmapWidth = (int) ((pageWidth * scale) + 0.5); - int bitmapHeight = (int) ((pageHeight * scale) + 0.5); + double scaleY = scaleFactor + * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) + / userAgent.getTargetPixelUnitToMillimeter(); + if (getPageViewport(pageNum).getForeignAttributes() != null) { + String scale = (String) getPageViewport(pageNum).getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX *= scales.getX(); + scaleY *= scales.getY(); + } + } + int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5); + int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5); return new Dimension(bitmapWidth, bitmapHeight); } diff --git a/src/java/org/apache/fop/render/extensions/prepress/PageBoundaries.java b/src/java/org/apache/fop/render/extensions/prepress/PageBoundaries.java new file mode 100644 index 000000000..8001e1fc2 --- /dev/null +++ b/src/java/org/apache/fop/render/extensions/prepress/PageBoundaries.java @@ -0,0 +1,235 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.extensions.prepress; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.text.MessageFormat; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.properties.FixedLength; + +/** + * This class is used to calculate the effective boundaries of a page including special-purpose + * boxes used in prepress. These are specified using extension attributes: + * bleedBox, trimBox and cropBox. The semantics are further described on the website. + */ +public class PageBoundaries { + + /** + * The extension attribute for calculating the PDF BleedBox area - specifies the bleed width. + */ + public static final QName EXT_BLEED + = new QName(ExtensionElementMapping.URI, null, "bleed"); + + /** + * The extension attribute for the PDF CropBox area. + */ + public static final QName EXT_CROP_OFFSET + = new QName(ExtensionElementMapping.URI, null, "crop-offset"); + + /** + * The extension attribute for the PDF CropBox area. + */ + public static final QName EXT_CROP_BOX + = new QName(ExtensionElementMapping.URI, null, "crop-box"); + + + private static final Pattern SIZE_UNIT_PATTERN + = Pattern.compile("^(-?\\d*\\.?\\d*)(px|in|cm|mm|pt|pc|mpt)$"); + + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + private Rectangle trimBox; + private Rectangle bleedBox; + private Rectangle mediaBox; + private Rectangle cropBox; + + /** + * Creates a new instance. + * @param pageSize the page size (in mpt) defined by the simple-page-master. + * @param bleed the bleed value (raw value as given in the property value) + * @param cropOffset the crop-offset value (raw value as given in the property value) + * @param cropBoxSelector the crop-box, valid values: (trim-box|bleed-box|media-box) + */ + public PageBoundaries(Dimension pageSize, String bleed, String cropOffset, + String cropBoxSelector) { + calculate(pageSize, bleed, cropOffset, cropBoxSelector); + } + + /** + * Creates a new instance. + * @param pageSize the page size (in mpt) defined by the simple-page-master. + * @param foreignAttributes the foreign attributes for the page + * (used to extract the extension attribute values) + */ + public PageBoundaries(Dimension pageSize, Map foreignAttributes) { + String bleed = (String)foreignAttributes.get(EXT_BLEED); + String cropOffset = (String)foreignAttributes.get(EXT_CROP_OFFSET); + String cropBoxSelector = (String)foreignAttributes.get(EXT_CROP_BOX); + calculate(pageSize, bleed, cropOffset, cropBoxSelector); + } + + private void calculate(Dimension pageSize, String bleed, String cropOffset, + String cropBoxSelector) { + this.trimBox = new Rectangle(pageSize); + this.bleedBox = getBleedBoxRectangle(this.trimBox, bleed); + Rectangle cropMarksBox = getCropMarksAreaRectangle(trimBox, cropOffset); + + //MediaBox includes all of the following three rectangles + this.mediaBox = new Rectangle(); + this.mediaBox.add(this.trimBox); + this.mediaBox.add(this.bleedBox); + this.mediaBox.add(cropMarksBox); + + if ("trim-box".equals(cropBoxSelector)) { + this.cropBox = this.trimBox; + } else if ("bleed-box".equals(cropBoxSelector)) { + this.cropBox = this.bleedBox; + } else if ("media-box".equals(cropBoxSelector) + || cropBoxSelector == null + || "".equals(cropBoxSelector)) { + this.cropBox = this.mediaBox; + } else { + final String err = "The crop-box has invalid value: {0}, " + + "possible values of crop-box: (trim-box|bleed-box|media-box)"; + throw new IllegalArgumentException(MessageFormat.format(err, + new Object[]{cropBoxSelector})); + } + } + + /** + * Returns the trim box for the page. This is equal to the page size given in XSL-FO. + * After production the printed media is trimmed to this rectangle. + * @return the trim box + */ + public Rectangle getTrimBox() { + return this.trimBox; + } + + /** + * Returns the bleed box for the page. + * @return the bleed box + */ + public Rectangle getBleedBox() { + return this.bleedBox; + } + + /** + * Returns the media box for the page. + * @return the media box + */ + public Rectangle getMediaBox() { + return this.mediaBox; + } + + /** + * Returns the crop box for the page. The crop box is used by Adobe Acrobat to select which + * parts of the document shall be displayed and it also defines the rectangle to which a + * RIP will clip the document. For bitmap output, this defines the size of the bitmap. + * @return the crop box + */ + public Rectangle getCropBox() { + return this.cropBox; + } + + /** + * The BleedBox is calculated by expanding the TrimBox by the bleed widths. + * + * @param trimBox the TrimBox rectangle + * @param bleed the given bleed widths + * @return the calculated BleedBox rectangle + */ + private static Rectangle getBleedBoxRectangle(Rectangle trimBox, String bleed) { + return getRectangleUsingOffset(trimBox, bleed); + } + + /** + * The MediaBox is calculated by expanding the TrimBox by the crop offsets. + * + * @param trimBox the TrimBox rectangle + * @param cropOffsets the given crop offsets + * @return the calculated MediaBox rectangle + */ + private static Rectangle getCropMarksAreaRectangle(Rectangle trimBox, String cropOffsets) { + return getRectangleUsingOffset(trimBox, cropOffsets); + } + + private static Rectangle getRectangleUsingOffset(Rectangle originalRect, String offset) { + if (offset == null || "".equals(offset) || originalRect == null) { + return originalRect; + } + + String[] offsets = WHITESPACE_PATTERN.split(offset); + int[] coords = new int[4]; // top, right, bottom, left + switch (offsets.length) { + case 1: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = coords[0]; + coords[2] = coords[0]; + coords[3] = coords[0]; + break; + case 2: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = coords[0]; + coords[3] = coords[1]; + break; + case 3: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = getLengthIntValue(offsets[2]); + coords[3] = coords[1]; + break; + case 4: + coords[0] = getLengthIntValue(offsets[0]); + coords[1] = getLengthIntValue(offsets[1]); + coords[2] = getLengthIntValue(offsets[2]); + coords[3] = getLengthIntValue(offsets[3]); + break; + default: + // TODO throw appropriate exception that can be caught by the event + // notification mechanism + throw new IllegalArgumentException("Too many arguments"); + } + return new Rectangle(originalRect.x - coords[3], + originalRect.y - coords[0], + originalRect.width + coords[3] + coords[1], + originalRect.height + coords[0] + coords[2]); + } + + private static int getLengthIntValue(final String length) { + final String err = "Incorrect length value: {0}"; + Matcher m = SIZE_UNIT_PATTERN.matcher(length); + + if (m.find()) { + return FixedLength.getInstance(Double.parseDouble(m.group(1)), + m.group(2)).getLength().getValue(); + } else { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{length})); + } + } + +} diff --git a/src/java/org/apache/fop/render/extensions/prepress/PageScale.java b/src/java/org/apache/fop/render/extensions/prepress/PageScale.java new file mode 100644 index 000000000..361423753 --- /dev/null +++ b/src/java/org/apache/fop/render/extensions/prepress/PageScale.java @@ -0,0 +1,92 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.extensions.prepress; + +import java.awt.geom.Point2D; +import java.text.MessageFormat; +import java.util.regex.Pattern; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.fo.extensions.ExtensionElementMapping; + +/** + * This class provides utility methods to parse the 'fox:scale' extension attribute. + */ +public final class PageScale { + + /** + * The extension 'scale' attribute for the simple-page-master element. + */ + public static final QName EXT_PAGE_SCALE + = new QName(ExtensionElementMapping.URI, null, "scale"); + + private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + /** + * Utility classes should not have a public or default constructor + */ + private PageScale() { + } + + /** + * Compute scale parameters from given fox:scale attribute which has the format: scaleX [scaleY] + * If scaleY is not defined, it equals scaleX. + * @param scale scale attribute, input format: scaleX [scaleY] + * @return the pair of (sx, sy) values + */ + public static Point2D getScale(String scale) { + // TODO throw appropriate exceptions that can be caught by the event + // notification mechanism + final String err = "Extension 'scale' attribute has incorrect value(s): {0}"; + + if (scale == null || scale.equals("")) { + return null; + } + + String[] scales = WHITESPACE_PATTERN.split(scale); + double scaleX; + try { + scaleX = Double.parseDouble(scales[0]); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + double scaleY; + switch (scales.length) { + case 1: + scaleY = scaleX; + break; + case 2: + try { + scaleY = Double.parseDouble(scales[1]); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + break; + default: + throw new IllegalArgumentException("Too many arguments"); + } + if (scaleX <= 0 || scaleY <= 0) { + throw new IllegalArgumentException(MessageFormat.format(err, new Object[]{scale})); + } + + return new Point2D.Double(scaleX, scaleY); + } +} diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 7c0bf8abc..2a23d06f8 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -563,10 +563,8 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } try { pageIndices.put(page.getKey(), new Integer(page.getPageIndex())); - Rectangle2D viewArea = page.getViewArea(); - Dimension dim = new Dimension( - (int)Math.ceil(viewArea.getWidth()), - (int)Math.ceil(viewArea.getHeight())); + Rectangle viewArea = page.getViewArea(); + Dimension dim = new Dimension(viewArea.width, viewArea.height); establishForeignAttributes(page.getForeignAttributes()); documentHandler.startPage(page.getPageIndex(), page.getPageNumberString(), diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java index 8516277c5..376130838 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java @@ -106,9 +106,17 @@ public class DocumentNavigationHandler extends DefaultHandler } else { String id = attributes.getValue("id"); int pageIndex = XMLUtil.getAttributeAsInt(attributes, "page-index"); - int x = XMLUtil.getAttributeAsInt(attributes, "x"); - int y = XMLUtil.getAttributeAsInt(attributes, "y"); - action = new GoToXYAction(id, pageIndex, new Point(x, y)); + final Point location; + if (pageIndex < 0) { + location = null; + } else { + final int x = XMLUtil + .getAttributeAsInt(attributes, "x"); + final int y = XMLUtil + .getAttributeAsInt(attributes, "y"); + location = new Point(x, y); + } + action = new GoToXYAction(id, pageIndex, location); } objectStack.push(action); } else if (GOTO_URI.getLocalName().equals(localName)) { diff --git a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java index 7efb82a12..a2b4f31b6 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java @@ -71,18 +71,34 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx /** * Returns the page index of the target page. + * <p> + * This function will always return a valid value for safety. Use + * {@link #isComplete()} to check if the link is actually complete. + * * @return the page index (0-based) */ public int getPageIndex() { - return this.pageIndex; + if (this.pageIndex >= 0) { + return this.pageIndex; + } else { + return 0; + } } /** * Returns the absolute coordinates of the target location on the page. + * <p> + * This function will always return a valid value for safety. Use + * {@link #isComplete()} to check if the link is actually complete. + * * @return the target location (coordinates in millipoints) */ public Point getTargetLocation() { - return this.targetLocation; + if (this.targetLocation == null) { + return new Point(0, 0); + } else { + return this.targetLocation; + } } /** @@ -93,9 +109,13 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx this.targetLocation = location; } + private boolean isCompleteExceptTargetLocation() { + return (getPageIndex() >= 0); + } + /** {@inheritDoc} */ public boolean isComplete() { - return (getPageIndex() >= 0) && (getTargetLocation() != null); + return this.isCompleteExceptTargetLocation() && (this.targetLocation != null); } /** {@inheritDoc} */ @@ -107,10 +127,10 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx return false; } GoToXYAction otherAction = (GoToXYAction)other; - if (getPageIndex() != otherAction.getPageIndex()) { + if (this.pageIndex != otherAction.pageIndex) { return false; } - if (getTargetLocation() == null || otherAction.getTargetLocation() == null) { + if (this.targetLocation == null || otherAction.targetLocation == null) { return false; } if (!getTargetLocation().equals(otherAction.getTargetLocation())) { @@ -121,16 +141,16 @@ public class GoToXYAction extends AbstractAction implements DocumentNavigationEx /** {@inheritDoc} */ public void toSAX(ContentHandler handler) throws SAXException { - if (getTargetLocation() == null) { - setTargetLocation(new Point(0, 0)); - } AttributesImpl atts = new AttributesImpl(); - if (isComplete()) { + if (this.isCompleteExceptTargetLocation()) { + final Point reportedTargetLocation = this.getTargetLocation(); atts.addAttribute(null, "id", "id", XMLUtil.CDATA, getID()); atts.addAttribute(null, "page-index", "page-index", XMLUtil.CDATA, Integer.toString(pageIndex)); - atts.addAttribute(null, "x", "x", XMLUtil.CDATA, Integer.toString(targetLocation.x)); - atts.addAttribute(null, "y", "y", XMLUtil.CDATA, Integer.toString(targetLocation.y)); + atts.addAttribute(null, "x", "x", XMLUtil.CDATA, + Integer.toString(reportedTargetLocation.x)); + atts.addAttribute(null, "y", "y", XMLUtil.CDATA, + Integer.toString(reportedTargetLocation.y)); } else { atts.addAttribute(null, "idref", "idref", XMLUtil.CDATA, getID()); } diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index 933398125..f09794ff2 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -24,6 +24,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; @@ -75,6 +76,8 @@ import org.apache.fop.fonts.Typeface; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.extensions.prepress.PageBoundaries; +import org.apache.fop.render.extensions.prepress.PageScale; import org.apache.fop.render.pdf.CTMHelper; import org.apache.fop.util.CharUtilities; import org.apache.fop.util.ColorUtil; @@ -290,7 +293,10 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem this.currentPageViewport = pageViewport; try { - Rectangle2D bounds = pageViewport.getViewArea(); + PageBoundaries boundaries = new PageBoundaries( + pageViewport.getViewArea().getSize(), pageViewport.getForeignAttributes()); + Rectangle bounds = boundaries.getCropBox(); + Rectangle bleedBox = boundaries.getBleedBox(); this.pageWidth = (int) Math.round(bounds.getWidth() / 1000f); this.pageHeight = (int) Math.round(bounds.getHeight() / 1000f); @@ -299,11 +305,25 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem + " (pageWidth " + pageWidth + ", pageHeight " + pageHeight + ")"); - double scale = scaleFactor + // set scale factor + double scaleX = scaleFactor; + double scaleY = scaleFactor; + String scale = (String) currentPageViewport.getForeignAttributes().get( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX *= scales.getX(); + scaleY *= scales.getY(); + } + + scaleX = scaleX + * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) + / userAgent.getTargetPixelUnitToMillimeter(); + scaleY = scaleY * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION) / userAgent.getTargetPixelUnitToMillimeter(); - int bitmapWidth = (int) ((pageWidth * scale) + 0.5); - int bitmapHeight = (int) ((pageHeight * scale) + 0.5); + int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5); + int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5); BufferedImage currentPageImage = getBufferedImage(bitmapWidth, bitmapHeight); @@ -326,20 +346,27 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem // transform page based on scale factor supplied AffineTransform at = graphics.getTransform(); - at.scale(scale, scale); + at.scale(scaleX, scaleY); + at.translate(bounds.getMinX() / -1000f, bounds.getMinY() / -1000f); graphics.setTransform(at); // draw page frame if (!transparentPageBackground) { graphics.setColor(Color.white); - graphics.fillRect(0, 0, pageWidth, pageHeight); + graphics.fillRect( + (int)Math.round(bleedBox.getMinX() / 1000f), + (int)Math.round(bleedBox.getMinY() / 1000f), + (int)Math.round(bleedBox.getWidth() / 1000f), + (int)Math.round(bleedBox.getHeight() / 1000f)); } + /* why did we have this??? graphics.setColor(Color.black); graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + */ state = new Java2DGraphicsState(graphics, this.fontInfo, at); try { diff --git a/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java b/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java index e4923b2c3..2ba3582e6 100644 --- a/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java +++ b/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java @@ -186,6 +186,13 @@ public class PCLDocumentHandler extends AbstractBinaryWritingIFDocumentHandler gen.selectPaperSource(Integer.parseInt(paperSource.toString())); } + //Output bin + Object outputBin = getContext().getForeignAttribute( + PCLElementMapping.PCL_OUTPUT_BIN); + if (outputBin != null) { + gen.selectOutputBin(Integer.parseInt(outputBin.toString())); + } + // Is Page duplex? Object pageDuplex = getContext().getForeignAttribute( PCLElementMapping.PCL_DUPLEX_MODE); diff --git a/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java b/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java index 797daa3a1..85653e423 100644 --- a/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java +++ b/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java @@ -30,7 +30,10 @@ import org.apache.fop.render.intermediate.IFDocumentHandler; */ public class PCLDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker { - private static final String[] MIMES = new String[] {MimeConstants.MIME_PCL}; + private static final String[] MIMES = new String[] { + MimeConstants.MIME_PCL, + MimeConstants.MIME_PCL_ALT + }; /** {@inheritDoc} */ public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) { diff --git a/src/java/org/apache/fop/render/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java index c36d2a66e..f89c03add 100644 --- a/src/java/org/apache/fop/render/pcl/PCLGenerator.java +++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java @@ -48,6 +48,7 @@ import org.apache.xmlgraphics.image.GraphicsUtil; import org.apache.xmlgraphics.util.UnitConv; import org.apache.fop.util.bitmap.BitmapImageUtil; +import org.apache.fop.util.bitmap.DitherUtil; import org.apache.fop.util.bitmap.MonochromeBitmapConverter; /** @@ -65,11 +66,6 @@ public class PCLGenerator { /** A list of all supported resolutions in PCL (values in dpi) */ public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600}; - /** Selects a 4x4 Bayer dither matrix (17 grayscales) */ - public static final int DITHER_MATRIX_4X4 = 4; - /** Selects a 8x8 Bayer dither matrix (65 grayscales) */ - public static final int DITHER_MATRIX_8X8 = 8; - private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); private final DecimalFormat df2 = new DecimalFormat("0.##", symbols); private final DecimalFormat df4 = new DecimalFormat("0.####", symbols); @@ -244,6 +240,18 @@ public class PCLGenerator { } /** + * Selects the output bin. The parameter is usually printer-specific. Usually, "1" is the + * default output bin (upper bin) and "2" is the lower (rear) output bin. Some printers + * may support additional output bins. Consult the technical reference for your printer + * for all available values. + * @param selector the integer representing the output bin + * @throws IOException In case of an I/O error + */ + public void selectOutputBin(int selector) throws IOException { + writeCommand("&l" + selector + "G"); + } + + /** * Selects the duplexing mode for the page. * The parameter is usually printer-specific. * "0" means Simplex, @@ -390,7 +398,7 @@ public class PCLGenerator { writeCommand("*c" + lineshade + "G"); writeCommand("*c2P"); //Shaded fill } else { - defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4); + defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4); writeCommand("*c" + formatDouble4(w / 100.0) + "h" + formatDouble4(h / 100.0) + "V"); @@ -401,34 +409,6 @@ public class PCLGenerator { setPatternTransparencyMode(true); } - //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix) - private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1}; - private static final int[] BAYER_D4; - private static final int[] BAYER_D8; - - static { - BAYER_D4 = deriveBayerMatrix(BAYER_D2); - BAYER_D8 = deriveBayerMatrix(BAYER_D4); - } - - private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) { - int xoff = (part & 1) * half; - int yoff = (part & 2) * half * half; - int matrixIndex = yoff + ((idx / half) * half * 2) + (idx % half) + xoff; - dn[matrixIndex] = value; - } - - private static int[] deriveBayerMatrix(int[] d) { - int[] dn = new int[d.length * 4]; - int half = (int)Math.sqrt(d.length); - for (int part = 0; part < 4; part++) { - for (int i = 0, c = d.length; i < c; i++) { - setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]); - } - } - return dn; - } - /** * Generates a user-defined pattern for a dithering pattern matching the grayscale value * of the color given. @@ -453,35 +433,12 @@ public class PCLGenerator { byte[] pattern; if (ditherMatrixSize == 8) { - int gray65 = gray255 * 65 / 255; - - pattern = new byte[BAYER_D8.length / 8]; - - for (int i = 0, c = BAYER_D8.length; i < c; i++) { - boolean dot = !(BAYER_D8[i] < gray65 - 1); - if (dot) { - int byteIdx = i / 8; - pattern[byteIdx] |= 1 << (i % 8); - } - } + pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_8X8, gray255, false); } else { - int gray17 = gray255 * 17 / 255; - //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an //8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern //instead of the 4x4 one. - pattern = new byte[BAYER_D4.length / 8 * 4]; - - for (int i = 0, c = BAYER_D4.length; i < c; i++) { - boolean dot = !(BAYER_D4[i] < gray17 - 1); - if (dot) { - int byteIdx = i / 4; - pattern[byteIdx] |= 1 << (i % 4); - pattern[byteIdx] |= 1 << ((i % 4) + 4); - pattern[byteIdx + 4] |= 1 << (i % 4); - pattern[byteIdx + 4] |= 1 << ((i % 4) + 4); - } - } + pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_4X4, gray255, true); } data.write(pattern); if ((baout.size() % 2) > 0) { @@ -564,7 +521,7 @@ public class PCLGenerator { if (usePCLShades) { selectCurrentPattern(convertToPCLShade(col), 2); } else { - defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4); + defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4); selectCurrentPattern(32, 4); } } diff --git a/src/java/org/apache/fop/render/pcl/PCLPageDefinition.java b/src/java/org/apache/fop/render/pcl/PCLPageDefinition.java index 54ad1e73a..3e577cfa0 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPageDefinition.java +++ b/src/java/org/apache/fop/render/pcl/PCLPageDefinition.java @@ -177,6 +177,9 @@ public class PCLPageDefinition { pageDefinitions.add(new PCLPageDefinition("Ledger", 6, createPhysicalPageSizeInch(11, 17), createLogicalPageRect(75, 0, 3150, 5100), false)); + pageDefinitions.add(new PCLPageDefinition("A5", 25, + createPhysicalPageSizeMm(148, 210), + createLogicalPageRect(71, 0, 1745, 2480), false)); pageDefinitions.add(new PCLPageDefinition("A4", 26, createPhysicalPageSizeMm(210, 297), createLogicalPageRect(71, 0, 2338, 3507), false)); @@ -198,6 +201,9 @@ public class PCLPageDefinition { pageDefinitions.add(new PCLPageDefinition("LedgerL", 6, createPhysicalPageSizeInch(17, 11), createLogicalPageRect(60, 0, 4980, 3300), true)); + pageDefinitions.add(new PCLPageDefinition("A5L", 25, + createPhysicalPageSizeMm(210, 148), + createLogicalPageRect(59, 0, 2362, 1747), true)); pageDefinitions.add(new PCLPageDefinition("A4L", 26, createPhysicalPageSizeMm(297, 210), createLogicalPageRect(59, 0, 3389, 2480), true)); diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderer.java b/src/java/org/apache/fop/render/pcl/PCLRenderer.java index 0c1373fb8..7b48dbccf 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRenderer.java +++ b/src/java/org/apache/fop/render/pcl/PCLRenderer.java @@ -289,6 +289,12 @@ public class PCLRenderer extends PrintRenderer implements PCLConstants { gen.selectPaperSource(Integer.parseInt(paperSource)); } + //Output bin + String outputBin = page.getForeignAttributeValue(PCLElementMapping.PCL_OUTPUT_BIN); + if (outputBin != null) { + gen.selectOutputBin(Integer.parseInt(outputBin)); + } + // Is Page duplex? String pageDuplex = page.getForeignAttributeValue(PCLElementMapping.PCL_DUPLEX_MODE); if (pageDuplex != null) { diff --git a/src/java/org/apache/fop/render/pcl/extensions/PCLElementMapping.java b/src/java/org/apache/fop/render/pcl/extensions/PCLElementMapping.java index 53931f671..700a95c65 100644 --- a/src/java/org/apache/fop/render/pcl/extensions/PCLElementMapping.java +++ b/src/java/org/apache/fop/render/pcl/extensions/PCLElementMapping.java @@ -40,6 +40,10 @@ public class PCLElementMapping extends ElementMapping { public static final QName PCL_PAPER_SOURCE = new QName(PCLElementMapping.NAMESPACE, null, "paper-source"); + /** The extension attribute for the PCL output bin */ + public static final QName PCL_OUTPUT_BIN + = new QName(PCLElementMapping.NAMESPACE, null, "output-bin"); + /** The extension attribute for the PCL duplex mode */ public static final QName PCL_DUPLEX_MODE = new QName(PCLElementMapping.NAMESPACE, null, "duplex-mode"); diff --git a/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java index cbaac4e55..165236359 100644 --- a/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java +++ b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java @@ -129,12 +129,12 @@ public abstract class AbstractImageAdapter implements PDFImage { if (cs == null && desc.startsWith("sRGB")) { //It's the default sRGB profile which we mapped to DefaultRGB in PDFRenderer cs = doc.getResources().getColorSpace("DefaultRGB"); - if (cs == null) { - //sRGB hasn't been set up for the PDF document - //so install but don't set to DefaultRGB - cs = PDFICCBasedColorSpace.setupsRGBColorSpace(doc); - } } + if (cs == null) { + // sRGB hasn't been set up for the PDF document + // so install but don't set to DefaultRGB + cs = PDFICCBasedColorSpace.setupsRGBColorSpace(doc); + } pdfICCStream = cs.getICCStream(); } return pdfICCStream; diff --git a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java index 5ddcd06c6..27d25e15b 100644 --- a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java +++ b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java @@ -19,6 +19,8 @@ package org.apache.fop.render.pdf; import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.RenderedImage; @@ -97,9 +99,19 @@ public class ImageRenderedAdapter extends AbstractImageAdapter { } /** {@inheritDoc} */ + protected ICC_Profile getEffectiveICCProfile() { + ColorSpace cs = getImageColorSpace(); + if (cs instanceof ICC_ColorSpace) { + ICC_ColorSpace iccSpace = (ICC_ColorSpace)cs; + return iccSpace.getProfile(); + } else { + return null; + } + } + + /** {@inheritDoc} */ public void setup(PDFDocument doc) { RenderedImage ri = getImage().getRenderedImage(); - ColorModel cm = getEffectiveColorModel(); super.setup(doc); diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index 285542635..9e2b2cdb3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -20,7 +20,11 @@ package org.apache.fop.render.pdf; import java.awt.Dimension; +import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Double; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; @@ -37,15 +41,13 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; - import org.xml.sax.SAXException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.apache.xmlgraphics.xmp.Metadata; import org.apache.fop.apps.MimeConstants; @@ -64,6 +66,8 @@ import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFStructElem; import org.apache.fop.pdf.PDFStructTreeRoot; +import org.apache.fop.render.extensions.prepress.PageBoundaries; +import org.apache.fop.render.extensions.prepress.PageScale; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; @@ -382,11 +386,33 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { // this.pdfResources = this.pdfDoc.getResources(); + PageBoundaries boundaries = new PageBoundaries(size, getContext().getForeignAttributes()); + + Rectangle trimBox = boundaries.getTrimBox(); + Rectangle bleedBox = boundaries.getBleedBox(); + Rectangle mediaBox = boundaries.getMediaBox(); + Rectangle cropBox = boundaries.getCropBox(); + + // set scale attributes + double scaleX = 1; + double scaleY = 1; + String scale = (String) getContext().getForeignAttribute( + PageScale.EXT_PAGE_SCALE); + Point2D scales = PageScale.getScale(scale); + if (scales != null) { + scaleX = scales.getX(); + scaleY = scales.getY(); + } + this.currentPage = this.pdfDoc.getFactory().makePage( - this.pdfResources, - (int)Math.round(size.getWidth() / 1000), - (int)Math.round(size.getHeight() / 1000), index, - parentTreeKey); // used for accessibility + this.pdfResources, + index, + toPointAndScale(mediaBox, scaleX, scaleY), + toPointAndScale(cropBox, scaleX, scaleY), + toPointAndScale(bleedBox, scaleX, scaleY), + toPointAndScale(trimBox, scaleX, scaleY), + parentTreeKey); + pdfUtil.generatePageLabel(index, name); currentPageRef = new PageReference(currentPage, size); @@ -396,10 +422,18 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { this.currentPage); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, - size.height / 1000f); + (scaleY * size.height) / 1000f); + basicPageTransform.scale(scaleX, scaleY); generator.concatenate(basicPageTransform); } + private Double toPointAndScale(Rectangle box, double scaleX, double scaleY) { + return new Rectangle2D.Double(box.getX() * scaleX / 1000, + box.getY() * scaleY / 1000, + box.getWidth() * scaleX / 1000, + box.getHeight() * scaleY / 1000); + } + /** {@inheritDoc} */ public IFPainter startPageContent() throws IFException { return new PDFPainter(this); diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index 2a9a07534..138129334 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -155,15 +155,20 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler if (pdfAction != null) { return pdfAction; } else if (action instanceof GoToXYAction) { - GoToXYAction a = (GoToXYAction)action; - PDFGoTo pdfGoTo = new PDFGoTo(null); - getPDFDoc().assignObjectNumber(pdfGoTo); - if (action.isComplete()) { - updateTargetLocation(pdfGoTo, a); + pdfAction = (PDFAction) incompleteActions.get(action.getID()); + if (pdfAction != null) { + return pdfAction; } else { - this.incompleteActions.put(action.getID(), pdfGoTo); + GoToXYAction a = (GoToXYAction)action; + PDFGoTo pdfGoTo = new PDFGoTo(null); + getPDFDoc().assignObjectNumber(pdfGoTo); + if (action.isComplete()) { + updateTargetLocation(pdfGoTo, a); + } else { + this.incompleteActions.put(action.getID(), pdfGoTo); + } + return pdfGoTo; } - return pdfGoTo; } else if (action instanceof URIAction) { URIAction u = (URIAction)action; assert u.isComplete(); diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java index 2c3be9736..d70409870 100644 --- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java @@ -30,7 +30,12 @@ import org.apache.fop.events.model.EventModel; public interface PDFEventProducer extends EventProducer { /** Provider class for the event producer. */ - class Provider { + final class Provider { + + /** + * Utility classes should not have a public or default constructor. + */ + private Provider() { } /** * Returns an event producer. diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml index 420f16a09..fd57d5099 100644 --- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml @@ -1,3 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> <message key="org.apache.fop.render.pdf.PDFEventProducer.nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message> </catalogue> diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index c40c94fc4..3b737150b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -457,9 +457,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf } currentPageRef = currentPage.referencePDF(); - Rectangle2D bounds = page.getViewArea(); - double h = bounds.getHeight(); - pageHeight = (int) h; + Rectangle bounds = page.getViewArea(); + pageHeight = bounds.height; this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage); this.borderPainter = new PDFBorderPainter(this.generator); diff --git a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java index 705515311..c1f3daf2f 100644 --- a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java +++ b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java @@ -28,20 +28,18 @@ import java.io.OutputStream; import org.w3c.dom.Document; import org.w3c.dom.svg.SVGLength; -import org.apache.avalon.framework.configuration.Configuration; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.UnitProcessor; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.ImageTranscoder; -import org.apache.xmlgraphics.java2d.TextHandler; import org.apache.xmlgraphics.java2d.ps.AbstractPSDocumentGraphics2D; -import org.apache.xmlgraphics.ps.PSGenerator; +import org.apache.fop.apps.FOPException; import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontSetup; import org.apache.fop.svg.AbstractFOPTranscoder; +import org.apache.fop.svg.PDFDocumentGraphics2DConfigurator; /** * This class enables to transcode an input to a PostScript document. @@ -72,9 +70,11 @@ import org.apache.fop.svg.AbstractFOPTranscoder; */ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { - private final Configuration cfg = null; + /** the root Graphics2D instance for generating PostScript */ protected AbstractPSDocumentGraphics2D graphics = null; + private FontInfo fontInfo; + /** * Constructs a new <tt>AbstractPSTranscoder</tt>. */ @@ -82,8 +82,21 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { super(); } + /** + * Creates the root Graphics2D instance for generating PostScript. + * @return the root Graphics2D + */ protected abstract AbstractPSDocumentGraphics2D createDocumentGraphics2D(); + /** {@inheritDoc} */ + protected boolean getAutoFontsDefault() { + //Currently set to false because auto-fonts requires a lot of memory in the PostScript + //case: All fonts (even the unsupported TTF fonts) need to be loaded and TrueType loading + //is currently very memory-intensive. At default JVM memory settings, this would result + //in OutOfMemoryErrors otherwise. + return false; + } + /** * Transcodes the specified Document as an image in the specified output. * @@ -98,11 +111,13 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { graphics = createDocumentGraphics2D(); if (!isTextStroked()) { - FontInfo fontInfo = new FontInfo(); - //TODO Do custom font configuration here somewhere/somehow - FontSetup.setup(fontInfo); - PSGenerator generator = graphics.getPSGenerator(); - graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); + try { + this.fontInfo = PDFDocumentGraphics2DConfigurator.createFontInfo( + getEffectiveConfiguration()); + graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo)); + } catch (FOPException fe) { + throw new TranscoderException(fe); + } } super.transcode(document, uri, output); @@ -146,21 +161,15 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder { /** {@inheritDoc} */ protected BridgeContext createBridgeContext() { + //For compatibility with Batik 1.6 + return createBridgeContext("1.x"); + } - BridgeContext ctx = new BridgeContext(userAgent); - if (!isTextStroked()) { - TextHandler handler = graphics.getCustomTextHandler(); - if (handler instanceof NativeTextHandler) { - NativeTextHandler nativeTextHandler = (NativeTextHandler)handler; - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - ctx.putBridge(new PSTextElementBridge(textPainter)); - } - } - - //ctx.putBridge(new PSImageElementBridge()); + /** {@inheritDoc} */ + public BridgeContext createBridgeContext(String version) { + BridgeContext ctx = new PSBridgeContext(userAgent, (isTextStroked() ? null : fontInfo), + getImageManager(), getImageSessionContext()); return ctx; } - } diff --git a/src/java/org/apache/fop/render/ps/FontResourceCache.java b/src/java/org/apache/fop/render/ps/FontResourceCache.java new file mode 100644 index 000000000..7d6f076a7 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/FontResourceCache.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.util.Map; + +import org.apache.xmlgraphics.ps.PSResource; + +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.Typeface; + +/** + * A cache for font resource objects. + */ +class FontResourceCache { + + private FontInfo fontInfo; + + /** This is a map of PSResource instances of all fonts defined (key: font key) */ + private Map fontResources = new java.util.HashMap(); + + public FontResourceCache(FontInfo fontInfo) { + this.fontInfo = fontInfo; + } + + /** + * Returns the PSResource for the given font key. + * @param key the font key ("F*") + * @return the matching PSResource + */ + public PSResource getPSResourceForFontKey(String key) { + PSResource res = null; + if (this.fontResources != null) { + res = (PSResource)this.fontResources.get(key); + } else { + this.fontResources = new java.util.HashMap(); + } + if (res == null) { + res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); + this.fontResources.put(key, res); + } + return res; + } + + private String getPostScriptNameForFontKey(String key) { + int pos = key.indexOf('_'); + String postFix = null; + if (pos > 0) { + postFix = key.substring(pos); + key = key.substring(0, pos); + } + Map fonts = fontInfo.getFonts(); + Typeface tf = (Typeface)fonts.get(key); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + if (tf == null) { + throw new IllegalStateException("Font not available: " + key); + } + if (postFix == null) { + return tf.getFontName(); + } else { + return tf.getFontName() + postFix; + } + } + + /** + * Adds a number of fonts to the cache. + * @param fontMap the font map + */ + public void addAll(Map fontMap) { + this.fontResources.putAll(fontMap); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java new file mode 100644 index 000000000..31571f987 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBatikFlowTextElementBridge.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.text.AttributedCharacterIterator; +import java.util.List; + +import org.apache.batik.extension.svg.BatikFlowTextElementBridge; +import org.apache.batik.extension.svg.FlowExtTextPainter; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; + +import org.apache.fop.fonts.FontInfo; + +/** + * Element Bridge for Batik's flow text extension, so those texts can be painted using + * PostScript primitives. + */ +public class PSBatikFlowTextElementBridge extends BatikFlowTextElementBridge { + + private PSTextPainter textPainter; + + /** + * Main Constructor. + * @param fontInfo the font directory + */ + public PSBatikFlowTextElementBridge(FontInfo fontInfo) { + this.textPainter = new PSFlowExtTextPainter(fontInfo); + } + + /** {@inheritDoc} */ + protected GraphicsNode instantiateGraphicsNode() { + GraphicsNode node = super.instantiateGraphicsNode(); + if (node != null) { + //Set our own text painter + ((TextNode)node).setTextPainter(getTextPainter()); + } + return node; + } + + /** + * Returns the text painter used by this bridge. + * @return the text painter + */ + public TextPainter getTextPainter() { + return this.textPainter; + } + + private class PSFlowExtTextPainter extends PSTextPainter { + + /** + * Main constructor + * @param fontInfo the font directory + */ + public PSFlowExtTextPainter(FontInfo fontInfo) { + super(fontInfo); + } + + /** {@inheritDoc} */ + public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { + //Text runs are delegated to the normal FlowExtTextPainter, we just paint the text. + FlowExtTextPainter delegate = (FlowExtTextPainter)FlowExtTextPainter.getInstance(); + return delegate.getTextRuns(node, aci); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSBridgeContext.java b/src/java/org/apache/fop/render/ps/PSBridgeContext.java new file mode 100644 index 000000000..1ec6acadf --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSBridgeContext.java @@ -0,0 +1,113 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.awt.geom.AffineTransform; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.DocumentLoader; +import org.apache.batik.bridge.SVGTextElementBridge; +import org.apache.batik.bridge.UserAgent; +import org.apache.batik.gvt.TextPainter; + +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.svg.AbstractFOPBridgeContext; + +/** + * BridgeContext which registers the custom bridges for PostScript output. + */ +public class PSBridgeContext extends AbstractFOPBridgeContext { + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param documentLoader the Document Loader to use for referenced documents. + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null + */ + public PSBridgeContext(UserAgent userAgent, DocumentLoader documentLoader, + FontInfo fontInfo, ImageManager imageManager, + ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent, documentLoader, fontInfo, + imageManager, imageSessionContext, linkTransform); + } + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + */ + public PSBridgeContext(UserAgent userAgent, FontInfo fontInfo, + ImageManager imageManager, ImageSessionContext imageSessionContext) { + super(userAgent, fontInfo, imageManager, imageSessionContext); + } + + /** {@inheritDoc} */ + public void registerSVGBridges() { + super.registerSVGBridges(); + + if (fontInfo != null) { + TextPainter textPainter = new PSTextPainter(fontInfo); + SVGTextElementBridge textElementBridge = new PSTextElementBridge(textPainter); + putBridge(textElementBridge); + + //Batik flow text extension (may not always be available) + //putBridge(new PDFBatikFlowTextElementBridge(fontInfo); + putElementBridgeConditional( + "org.apache.fop.render.ps.PSBatikFlowTextElementBridge", + "org.apache.batik.extension.svg.BatikFlowTextElementBridge"); + + //SVG 1.2 flow text support + //putBridge(new PDFSVG12TextElementBridge(fontInfo)); //-->Batik 1.7 + putElementBridgeConditional( + "org.apache.fop.render.ps.PSSVG12TextElementBridge", + "org.apache.batik.bridge.svg12.SVG12TextElementBridge"); + + //putBridge(new PDFSVGFlowRootElementBridge(fontInfo)); + putElementBridgeConditional( + "org.apache.fop.render.ps.PSSVGFlowRootElementBridge", + "org.apache.batik.bridge.svg12.SVGFlowRootElementBridge"); + } + + //putBridge(new PSImageElementBridge()); //TODO uncomment when implemented + } + + // Make sure any 'sub bridge contexts' also have our bridges. + //TODO There's no matching method in the super-class here + public BridgeContext createBridgeContext() { + return new PSBridgeContext(getUserAgent(), getDocumentLoader(), + fontInfo, + getImageManager(), + getImageSessionContext(), + linkTransform); + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java index 1379651c8..5a6db172a 100644 --- a/src/java/org/apache/fop/render/ps/PSDocumentHandler.java +++ b/src/java/org/apache/fop/render/ps/PSDocumentHandler.java @@ -50,8 +50,6 @@ import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; import org.apache.fop.apps.MimeConstants; -import org.apache.fop.fonts.LazyFont; -import org.apache.fop.fonts.Typeface; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; @@ -91,8 +89,8 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** Used to temporarily store PSSetupCode instance until they can be written. */ private List setupCodeList; - /** This is a map of PSResource instances of all fonts defined (key: font key) */ - private Map fontResources; + /** This is a cache of PSResource instances of all fonts defined */ + private FontResourceCache fontResources; /** This is a map of PSResource instances of all forms (key: uri) */ private Map formResources; @@ -139,6 +137,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); + this.fontResources = new FontResourceCache(getFontInfo()); try { OutputStream out; if (psUtil.isOptimizeResources()) { @@ -200,7 +199,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { gen.writeDSCComment(DSCConstants.BEGIN_SETUP); PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode"); if (!psUtil.isOptimizeResources()) { - this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); + this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo)); } else { gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass } @@ -436,8 +435,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void endPageContent() throws IFException { try { - //Show page - gen.writeln("showpage"); + gen.showPage(); } catch (IOException ioe) { throw new IFException("I/O error in endPageContent()", ioe); } @@ -534,45 +532,13 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } - private String getPostScriptNameForFontKey(String key) { - int pos = key.indexOf('_'); - String postFix = null; - if (pos > 0) { - postFix = key.substring(pos); - key = key.substring(0, pos); - } - Map fonts = fontInfo.getFonts(); - Typeface tf = (Typeface)fonts.get(key); - if (tf instanceof LazyFont) { - tf = ((LazyFont)tf).getRealFont(); - } - if (tf == null) { - throw new IllegalStateException("Font not available: " + key); - } - if (postFix == null) { - return tf.getFontName(); - } else { - return tf.getFontName() + postFix; - } - } - /** * Returns the PSResource for the given font key. * @param key the font key ("F*") * @return the matching PSResource */ protected PSResource getPSResourceForFontKey(String key) { - PSResource res = null; - if (this.fontResources != null) { - res = (PSResource)this.fontResources.get(key); - } else { - this.fontResources = new java.util.HashMap(); - } - if (res == null) { - res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); - this.fontResources.put(key, res); - } - return res; + return this.fontResources.getPSResourceForFontKey(key); } /** diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java index f6679e8da..41cba7563 100644 --- a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java @@ -65,16 +65,10 @@ public class PSImageHandlerSVG implements ImageHandler { PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); - NativeTextHandler nativeTextHandler = null; - BridgeContext ctx = new BridgeContext(ua); - if (!strokeText) { - nativeTextHandler = new NativeTextHandler(graphics, psContext.getFontInfo()); - graphics.setCustomTextHandler(nativeTextHandler); - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); - ctx.putBridge(tBridge); - } + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psContext.getFontInfo()), + context.getUserAgent().getFactory().getImageManager(), + context.getUserAgent().getImageSessionContext()); GraphicsNode root; try { diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index cb88f4670..051013a63 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -350,7 +350,6 @@ public class PSPainter extends AbstractIFPainter { //TODO Opportunity for font caching if font state is more heavily used String fontKey = getFontInfo().getInternalFontKey(triplet); int sizeMillipoints = state.getFontSize(); - float fontSize = sizeMillipoints / 1000f; // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontKey); @@ -360,9 +359,7 @@ public class PSPainter extends AbstractIFPainter { } Font font = getFontInfo().getFontInstance(triplet, sizeMillipoints); - PSResource res = this.documentHandler.getPSResourceForFontKey(fontKey); - generator.useFont("/" + res.getName(), fontSize); - generator.getResourceTracker().notifyResourceUsageOnPage(res); + useFont(fontKey, sizeMillipoints); generator.writeln("1 0 0 -1 " + formatMptAsPt(generator, x) + " " + formatMptAsPt(generator, y) + " Tm"); diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 19fcd8af8..14924996e 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -815,8 +815,8 @@ public class PSRenderer extends AbstractPathOrientedRenderer {page.getPageNumberString(), new Integer(this.currentPageNumber)}); - double pageWidth = Math.round(page.getViewArea().getWidth()) / 1000f; - double pageHeight = Math.round(page.getViewArea().getHeight()) / 1000f; + double pageWidth = page.getViewArea().width / 1000f; + double pageHeight = page.getViewArea().height / 1000f; boolean rotate = false; List pageSizes = new java.util.ArrayList(); if (getPSUtil().isAutoRotateLandscape() && (pageHeight < pageWidth)) { @@ -933,7 +933,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer super.renderPage(page); //Show page - writeln("showpage"); + gen.showPage(); gen.writeDSCComment(DSCConstants.PAGE_TRAILER); if (page.hasExtensionAttachments()) { List extensionAttachments = page.getExtensionAttachments(); diff --git a/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java new file mode 100644 index 000000000..56b1f91bd --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSSVGFlowRootElementBridge.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.text.AttributedCharacterIterator; +import java.util.List; + +import org.apache.batik.bridge.svg12.SVGFlowRootElementBridge; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; +import org.apache.batik.gvt.flow.FlowTextPainter; + +import org.apache.fop.fonts.FontInfo; + +/** + * Element Bridge for SVG 1.2 flow text, so those texts can be painted using + * PDF primitives. + */ +public class PSSVGFlowRootElementBridge extends SVGFlowRootElementBridge { + + private PSTextPainter textPainter; + + /** + * Main Constructor. + * @param fontInfo the font directory + */ + public PSSVGFlowRootElementBridge(FontInfo fontInfo) { + this.textPainter = new PSFlowTextPainter(fontInfo); + } + + /** {@inheritDoc} */ + protected GraphicsNode instantiateGraphicsNode() { + GraphicsNode node = super.instantiateGraphicsNode(); + if (node != null) { + //Set our own text painter + ((TextNode)node).setTextPainter(getTextPainter()); + } + return node; + } + + /** + * Returns the text painter used by this bridge. + * @return the text painter + */ + public TextPainter getTextPainter() { + return this.textPainter; + } + + private class PSFlowTextPainter extends PSTextPainter { + + /** + * Main constructor + * @param fontInfo the font directory + */ + public PSFlowTextPainter(FontInfo fontInfo) { + super(fontInfo); + } + + /** {@inheritDoc} */ + public List getTextRuns(TextNode node, AttributedCharacterIterator aci) { + //Text runs are delegated to the normal FlowTextPainter, we just paint the text. + FlowTextPainter delegate = (FlowTextPainter)FlowTextPainter.getInstance(); + return delegate.getTextRuns(node, aci); + } + + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSSVGHandler.java b/src/java/org/apache/fop/render/ps/PSSVGHandler.java index 75182682d..5cc1a1b01 100644 --- a/src/java/org/apache/fop/render/ps/PSSVGHandler.java +++ b/src/java/org/apache/fop/render/ps/PSSVGHandler.java @@ -259,17 +259,10 @@ public class PSSVGHandler extends AbstractGenericSVGHandler PSGraphics2D graphics = new PSGraphics2D(strokeText, gen); graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); - NativeTextHandler nativeTextHandler = null; - BridgeContext ctx = new BridgeContext(ua); - if (!strokeText) { - FontInfo fontInfo = psInfo.getFontInfo(); - nativeTextHandler = new NativeTextHandler(graphics, fontInfo); - graphics.setCustomTextHandler(nativeTextHandler); - PSTextPainter textPainter = new PSTextPainter(nativeTextHandler); - ctx.setTextPainter(textPainter); - PSTextElementBridge tBridge = new PSTextElementBridge(textPainter); - ctx.putBridge(tBridge); - } + BridgeContext ctx = new PSBridgeContext(ua, + (strokeText ? null : psInfo.fontInfo), + context.getUserAgent().getFactory().getImageManager(), + context.getUserAgent().getImageSessionContext()); //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) //to it. diff --git a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java index ab0c2d723..524fbdad8 100644 --- a/src/java/org/apache/fop/render/ps/PSTextElementBridge.java +++ b/src/java/org/apache/fop/render/ps/PSTextElementBridge.java @@ -19,13 +19,13 @@ package org.apache.fop.render.ps; -import org.apache.batik.bridge.SVGTextElementBridge; +import org.w3c.dom.Element; + import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.SVGTextElementBridge; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.TextNode; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; +import org.apache.batik.gvt.TextPainter; /** * Bridge class for the <text> element. @@ -37,13 +37,13 @@ import org.w3c.dom.Node; */ public class PSTextElementBridge extends SVGTextElementBridge { - private PSTextPainter textPainter; + private TextPainter textPainter; /** * Constructs a new bridge for the <text> element. * @param textPainter the text painter to use */ - public PSTextElementBridge(PSTextPainter textPainter) { + public PSTextElementBridge(TextPainter textPainter) { this.textPainter = textPainter; } @@ -56,60 +56,13 @@ public class PSTextElementBridge extends SVGTextElementBridge { */ public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { GraphicsNode node = super.createGraphicsNode(ctx, e); - /* this code is worthless I think. PSTextPainter does a much better job - * at determining whether to stroke or not. */ - if (true/*node != null && isSimple(ctx, e, node)*/) { - ((TextNode)node).setTextPainter(getTextPainter()); - } + ((TextNode)node).setTextPainter(getTextPainter()); return node; } - private PSTextPainter getTextPainter() { + private TextPainter getTextPainter() { return this.textPainter; } - /** - * Check if text element contains simple text. - * This checks the children of the text element to determine - * if the text is simple. The text is simple if it can be rendered - * with basic text drawing algorithms. This means there are no - * alternate characters, the font is known and there are no effects - * applied to the text. - * - * @param ctx the bridge context - * @param element the svg text element - * @param node the graphics node - * @return true if this text is simple of false if it cannot be - * easily rendered using normal drawString on the PDFGraphics2D - */ - private boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { - for (Node n = element.getFirstChild(); - n != null; - n = n.getNextSibling()) { - - switch (n.getNodeType()) { - case Node.ELEMENT_NODE: - - if (n.getLocalName().equals(SVG_TSPAN_TAG) - || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TREF_TAG)) { - return false; - } - break; - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - default: - } - } - - /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { - return false; - }*/ - - return true; - } } diff --git a/src/java/org/apache/fop/render/ps/PSTextPainter.java b/src/java/org/apache/fop/render/ps/PSTextPainter.java index a318c6465..018b6f9b7 100644 --- a/src/java/org/apache/fop/render/ps/PSTextPainter.java +++ b/src/java/org/apache/fop/render/ps/PSTextPainter.java @@ -19,555 +19,516 @@ package org.apache.fop.render.ps; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; -import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; import java.io.IOException; import java.text.AttributedCharacterIterator; -import java.text.CharacterIterator; import java.util.Iterator; import java.util.List; -import org.apache.batik.dom.svg.SVGOMTextElement; -import org.apache.batik.gvt.TextNode; -import org.apache.batik.gvt.TextPainter; -import org.apache.batik.gvt.font.GVTFontFamily; -import org.apache.batik.gvt.renderer.StrokingTextPainter; -import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; -import org.apache.batik.gvt.text.Mark; +import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.text.TextPaintInfo; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.fop.fonts.Font; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontTriplet; +import org.apache.batik.gvt.text.TextSpanLayout; + import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; +import org.apache.xmlgraphics.ps.PSGenerator; +import org.apache.xmlgraphics.ps.PSResource; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.svg.NativeTextPainter; +import org.apache.fop.util.CharUtilities; /** * Renders the attributed character iterator of a <tt>TextNode</tt>. - * This class draws the text directly into the PSGraphics2D so that + * This class draws the text directly using PostScript text operators so * the text is not drawn using shapes which makes the PS files larger. - * If the text is simple enough to draw then it sets the font and calls - * drawString. If the text is complex or the cannot be translated - * into a simple drawString the StrokingTextPainter is used instead. - * - * (todo) handle underline, overline and strikethrough - * (todo) use drawString(AttributedCharacterIterator iterator...) for some - * - * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> - * @version $Id$ + * <p> + * The text runs are split into smaller text runs that can be bundles in single + * calls of the xshow, yshow or xyshow operators. For outline text, the charpath + * operator is used. */ -public class PSTextPainter implements TextPainter { +public class PSTextPainter extends NativeTextPainter { - /** the logger for this class */ - protected Log log = LogFactory.getLog(PSTextPainter.class); + private static final boolean DEBUG = false; - private final NativeTextHandler nativeTextHandler; - private final FontInfo fontInfo; + private FontResourceCache fontResources; - /** - * Use the stroking text painter to get the bounds and shape. - * Also used as a fallback to draw the string with strokes. - */ - protected static final TextPainter - PROXY_PAINTER = StrokingTextPainter.getInstance(); + private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** * Create a new PS text painter with the given font information. - * @param nativeTextHandler the NativeTextHandler instance used for text painting + * @param fontInfo the font collection */ - public PSTextPainter(NativeTextHandler nativeTextHandler) { - this.nativeTextHandler = nativeTextHandler; - this.fontInfo = nativeTextHandler.getFontInfo(); + public PSTextPainter(FontInfo fontInfo) { + super(fontInfo); + this.fontResources = new FontResourceCache(fontInfo); } - /** - * Paints the specified attributed character iterator using the - * specified Graphics2D and context and font context. - * @param node the TextNode to paint - * @param g2d the Graphics2D to use - */ - public void paint(TextNode node, Graphics2D g2d) { - String txt = node.getText(); - Point2D loc = node.getLocation(); - - if (hasUnsupportedAttributes(node)) { - PROXY_PAINTER.paint(node, g2d); - } else { - paintTextRuns(node.getTextRuns(), g2d, loc); - } + /** {@inheritDoc} */ + protected boolean isSupported(Graphics2D g2d) { + return g2d instanceof PSGraphics2D; } + /** {@inheritDoc} */ + protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { + AttributedCharacterIterator runaci = textRun.getACI(); + runaci.first(); - private boolean hasUnsupportedAttributes(TextNode node) { - Iterator i = node.getTextRuns().iterator(); - while (i.hasNext()) { - StrokingTextPainter.TextRun - run = (StrokingTextPainter.TextRun)i.next(); - AttributedCharacterIterator aci = run.getACI(); - boolean hasUnsupported = hasUnsupportedAttributes(aci); - if (hasUnsupported) { - return true; - } + TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); + if (tpi == null || !tpi.visible) { + return; } - return false; - } - - private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { - boolean hasunsupported = false; - - String text = getText(aci); - Font font = makeFont(aci); - if (hasUnsupportedGlyphs(text, font)) { - log.trace("-> Unsupported glyphs found"); - hasunsupported = true; - } - - TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); - if ((tpi != null) - && ((tpi.strokeStroke != null && tpi.strokePaint != null) - || (tpi.strikethroughStroke != null) - || (tpi.underlineStroke != null) - || (tpi.overlineStroke != null))) { - log.trace("-> under/overlines etc. found"); - hasunsupported = true; - } - - //Alpha is not supported - Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND); - if (foreground instanceof Color) { - Color col = (Color)foreground; - if (col.getAlpha() != 255) { - log.trace("-> transparency found"); - hasunsupported = true; - } + if ((tpi != null) && (tpi.composite != null)) { + g2d.setComposite(tpi.composite); } - Object letSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); - if (letSpace != null) { - log.trace("-> letter spacing found"); - hasunsupported = true; - } + //------------------------------------ + TextSpanLayout layout = textRun.getLayout(); + logTextRun(runaci, layout); + CharSequence chars = collectCharacters(runaci); + runaci.first(); //Reset ACI - Object wordSpace = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); - if (wordSpace != null) { - log.trace("-> word spacing found"); - hasunsupported = true; - } + final PSGraphics2D ps = (PSGraphics2D)g2d; + final PSGenerator gen = ps.getPSGenerator(); + ps.preparePainting(); - Object lengthAdjust = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); - if (lengthAdjust != null) { - log.trace("-> length adjustments found"); - hasunsupported = true; + if (DEBUG) { + log.debug("Text: " + chars); + gen.commentln("%Text: " + chars); } - Object writeMod = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE); - if (writeMod != null - && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals( - writeMod)) { - log.trace("-> Unsupported writing modes found"); - hasunsupported = true; + GeneralPath debugShapes = null; + if (DEBUG) { + debugShapes = new GeneralPath(); } - Object vertOr = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); - if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( - vertOr)) { - log.trace("-> vertical orientation found"); - hasunsupported = true; + TextUtil textUtil = new TextUtil(gen); + textUtil.setupFonts(runaci); + if (!textUtil.hasFonts()) { + //Draw using Java2D when no native fonts are available + textRun.getLayout().draw(g2d); + return; } - Object rcDel = aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER); - //Batik 1.6 returns null here which makes it impossible to determine whether this can - //be painted or not, i.e. fall back to stroking. :-( - if (/*rcDel != null &&*/ !(rcDel instanceof SVGOMTextElement)) { - log.trace("-> spans found"); - hasunsupported = true; //Filter spans - } + gen.saveGraphicsState(); + gen.concatMatrix(g2d.getTransform()); + Shape imclip = g2d.getClip(); + clip(ps, imclip); + + gen.writeln("BT"); //beginTextObject() + + AffineTransform localTransform = new AffineTransform(); + Point2D prevPos = null; + GVTGlyphVector gv = layout.getGlyphVector(); + PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs + for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { + char ch = chars.charAt(index); + boolean visibleChar = gv.isGlyphVisible(index) + || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); + logCharacter(ch, layout, index, visibleChar); + if (!visibleChar) { + continue; + } + Point2D glyphPos = gv.getGlyphPosition(index); + + AffineTransform glyphTransform = gv.getGlyphTransform(index); + if (log.isTraceEnabled()) { + log.trace("pos " + glyphPos + ", transform " + glyphTransform); + } + if (DEBUG) { + Shape sh = gv.getGlyphLogicalBounds(index); + if (sh == null) { + sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); + } + debugShapes.append(sh, false); + } + + //Exact position of the glyph + localTransform.setToIdentity(); + localTransform.translate(glyphPos.getX(), glyphPos.getY()); + if (glyphTransform != null) { + localTransform.concatenate(glyphTransform); + } + localTransform.scale(1, -1); + + boolean flushCurrentRun = false; + //Try to optimize by combining characters using the same font and on the same line. + if (glyphTransform != null) { + //Happens for text-on-a-path + flushCurrentRun = true; + } + if (psRun.getRunLength() >= 128) { + //Don't let a run get too long + flushCurrentRun = true; + } + + //Note the position of the glyph relative to the previous one + Point2D relPos; + if (prevPos == null) { + relPos = new Point2D.Double(0, 0); + } else { + relPos = new Point2D.Double( + glyphPos.getX() - prevPos.getX(), + glyphPos.getY() - prevPos.getY()); + } + if (psRun.vertChanges == 0 + && psRun.getHorizRunLength() > 2 + && relPos.getY() != 0) { + //new line + flushCurrentRun = true; + } - if (hasunsupported) { - log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); + //Select the actual character to paint + char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); + + //Select (sub)font for character + Font f = textUtil.selectFontForChar(paintChar); + char mapped = f.mapChar(ch); + boolean fontChanging = textUtil.isFontChanging(f, mapped); + if (fontChanging) { + flushCurrentRun = true; + } + + if (flushCurrentRun) { + //Paint the current run and reset for the next run + psRun.paint(ps, textUtil, tpi); + psRun.reset(); + } + + //Track current run + psRun.addCharacter(paintChar, relPos); + psRun.noteStartingTransformation(localTransform); + + //Change font if necessary + if (fontChanging) { + textUtil.setCurrentFont(f, mapped); + } + + //Update last position + prevPos = glyphPos; + } + psRun.paint(ps, textUtil, tpi); + gen.writeln("ET"); //endTextObject() + gen.restoreGraphicsState(); + + if (DEBUG) { + //Paint debug shapes + g2d.setStroke(new BasicStroke(0)); + g2d.setColor(Color.LIGHT_GRAY); + g2d.draw(debugShapes); } - return hasunsupported; } - /** - * Paint a list of text runs on the Graphics2D at a given location. - * @param textRuns the list of text runs - * @param g2d the Graphics2D to paint to - * @param loc the current location of the "cursor" - */ - protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) { - Point2D currentloc = loc; - Iterator i = textRuns.iterator(); - while (i.hasNext()) { - StrokingTextPainter.TextRun - run = (StrokingTextPainter.TextRun)i.next(); - currentloc = paintTextRun(run, g2d, currentloc); + private void applyColor(Paint paint, final PSGenerator gen) throws IOException { + if (paint == null) { + return; + } else if (paint instanceof Color) { + Color col = (Color)paint; + gen.useColor(col); + } else { + log.warn("Paint not supported: " + paint.toString()); } } - /** - * Paint a single text run on the Graphics2D at a given location. - * @param run the text run to paint - * @param g2d the Graphics2D to paint to - * @param loc the current location of the "cursor" - * @return the new location of the "cursor" after painting the text run - */ - protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) { - AttributedCharacterIterator aci = run.getACI(); - return paintACI(aci, g2d, loc); + private PSResource getResourceForFont(Font f, String postfix) { + String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); + return this.fontResources.getPSResourceForFontKey(key); } - /** - * Extract the raw text from an ACI. - * @param aci ACI to inspect - * @return the extracted text - */ - protected String getText(AttributedCharacterIterator aci) { - StringBuffer sb = new StringBuffer(aci.getEndIndex() - aci.getBeginIndex()); - for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) { - sb.append(c); + private void clip(PSGraphics2D ps, Shape shape) throws IOException { + if (shape == null) { + return; } - return sb.toString(); + ps.getPSGenerator().writeln("newpath"); + PathIterator iter = shape.getPathIterator(IDENTITY_TRANSFORM); + ps.processPathIterator(iter); + ps.getPSGenerator().writeln("clip"); } - /** - * Paint an ACI on a Graphics2D at a given location. The method has to - * update the location after painting. - * @param aci ACI to paint - * @param g2d Graphics2D to paint on - * @param loc start location - * @return new current location - */ - protected Point2D paintACI(AttributedCharacterIterator aci, Graphics2D g2d, Point2D loc) { - //ACIUtils.dumpAttrs(aci); + private class TextUtil { - aci.first(); + private PSGenerator gen; + private Font[] fonts; + private Font currentFont; + private int currentEncoding = -1; - updateLocationFromACI(aci, loc); - - TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); - - if (tpi == null) { - return loc; + public TextUtil(PSGenerator gen) { + this.gen = gen; } - TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); + public Font selectFontForChar(char ch) { + for (int i = 0, c = fonts.length; i < c; i++) { + if (fonts[i].hasChar(ch)) { + return fonts[i]; + } + } + return fonts[0]; //TODO Maybe fall back to painting with shapes + } - //Set up font - List gvtFonts = (List)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); - Paint foreground = tpi.fillPaint; - Paint strokePaint = tpi.strokePaint; - Stroke stroke = tpi.strokeStroke; + public void writeTextMatrix(AffineTransform transform) throws IOException { + double[] matrix = new double[6]; + transform.getMatrix(matrix); + gen.writeln(gen.formatDouble5(matrix[0]) + " " + + gen.formatDouble5(matrix[1]) + " " + + gen.formatDouble5(matrix[2]) + " " + + gen.formatDouble5(matrix[3]) + " " + + gen.formatDouble5(matrix[4]) + " " + + gen.formatDouble5(matrix[5]) + " Tm"); + } - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - return loc; + public boolean isFontChanging(Font f, char mapped) { + if (f != getCurrentFont()) { + int encoding = mapped / 256; + if (encoding != getCurrentFontEncoding()) { + return true; //Font is changing + } + } + return false; //Font is the same } - Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); - Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); - if (foreground instanceof Color) { - Color col = (Color)foreground; - g2d.setColor(col); + public void selectFont(Font f, char mapped) throws IOException { + int encoding = mapped / 256; + String postfix = (encoding == 0 ? null : Integer.toString(encoding)); + PSResource res = getResourceForFont(f, postfix); + gen.useFont("/" + res.getName(), f.getFontSize() / 1000f); + gen.getResourceTracker().notifyResourceUsageOnPage(res); } - g2d.setPaint(foreground); - g2d.setStroke(stroke); - Font font = makeFont(aci); - java.awt.Font awtFont = makeAWTFont(aci, font); + public Font getCurrentFont() { + return this.currentFont; + } - g2d.setFont(awtFont); + public int getCurrentFontEncoding() { + return this.currentEncoding; + } - String txt = getText(aci); - float advance = getStringWidth(txt, font); - float tx = 0; - if (anchor != null) { - switch (anchor.getType()) { - case TextNode.Anchor.ANCHOR_MIDDLE: - tx = -advance / 2; - break; - case TextNode.Anchor.ANCHOR_END: - tx = -advance; - break; - default: //nop - } + public void setCurrentFont(Font font, int encoding) { + this.currentFont = font; + this.currentEncoding = encoding; } - drawPrimitiveString(g2d, loc, font, txt, tx); - loc.setLocation(loc.getX() + advance, loc.getY()); - return loc; - } + public void setCurrentFont(Font font, char mapped) { + int encoding = mapped / 256; + setCurrentFont(font, encoding); + } - protected void drawPrimitiveString(Graphics2D g2d, Point2D loc, Font font, String txt, float tx) { - //Finally draw text - nativeTextHandler.setOverrideFont(font); - try { - try { - nativeTextHandler.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY())); - } catch (IOException ioe) { - if (g2d instanceof PSGraphics2D) { - ((PSGraphics2D)g2d).handleIOException(ioe); - } - } - } finally { - nativeTextHandler.setOverrideFont(null); + public void setupFonts(AttributedCharacterIterator runaci) { + this.fonts = findFonts(runaci); } - } - private void updateLocationFromACI( - AttributedCharacterIterator aci, - Point2D loc) { - //Adjust position of span - Float xpos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.X); - Float ypos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.Y); - Float dxpos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.DX); - Float dypos = (Float)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.DY); - if (xpos != null) { - loc.setLocation(xpos.doubleValue(), loc.getY()); - } - if (ypos != null) { - loc.setLocation(loc.getX(), ypos.doubleValue()); - } - if (dxpos != null) { - loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); - } - if (dypos != null) { - loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); + public boolean hasFonts() { + return (fonts != null) && (fonts.length > 0); } - } - private String getStyle(AttributedCharacterIterator aci) { - Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); - return ((posture != null) && (posture.floatValue() > 0.0)) - ? "italic" - : "normal"; } - private int getWeight(AttributedCharacterIterator aci) { - Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); - return ((taWeight != null) && (taWeight.floatValue() > 1.0)) - ? Font.WEIGHT_BOLD - : Font.WEIGHT_NORMAL; - } + private class PSTextRun { - private Font makeFont(AttributedCharacterIterator aci) { - Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); - if (fontSize == null) { - fontSize = new Float(10.0f); - } - String style = getStyle(aci); - int weight = getWeight(aci); - - String fontFamily = null; - List gvtFonts = (List) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); - if (gvtFonts != null) { - Iterator i = gvtFonts.iterator(); - while (i.hasNext()) { - GVTFontFamily fam = (GVTFontFamily) i.next(); - /* (todo) Enable SVG Font painting - if (fam instanceof SVGFontFamily) { - PROXY_PAINTER.paint(node, g2d); - return; - }*/ - fontFamily = fam.getFamilyName(); - if (fontInfo.hasFont(fontFamily, style, weight)) { - FontTriplet triplet = fontInfo.fontLookup( - fontFamily, style, weight); - int fsize = (int)(fontSize.floatValue() * 1000); - return fontInfo.getFontInstance(triplet, fsize); - } - } + private AffineTransform textTransform; + private List relativePositions = new java.util.LinkedList(); + private StringBuffer currentChars = new StringBuffer(); + private int horizChanges = 0; + private int vertChanges = 0; + + public void reset() { + textTransform = null; + currentChars.setLength(0); + horizChanges = 0; + vertChanges = 0; + relativePositions.clear(); } - FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); - int fsize = (int)(fontSize.floatValue() * 1000); - return fontInfo.getFontInstance(triplet, fsize); - } - private java.awt.Font makeAWTFont(AttributedCharacterIterator aci, Font font) { - final String style = getStyle(aci); - final int weight = getWeight(aci); - int fStyle = java.awt.Font.PLAIN; - if (weight == Font.WEIGHT_BOLD) { - fStyle |= java.awt.Font.BOLD; + public int getHorizRunLength() { + if (this.vertChanges == 0 + && getRunLength() > 0) { + return getRunLength(); + } + return 0; } - if ("italic".equals(style)) { - fStyle |= java.awt.Font.ITALIC; + + public void addCharacter(char paintChar, Point2D relPos) { + addRelativePosition(relPos); + currentChars.append(paintChar); } - return new java.awt.Font(font.getFontName(), fStyle, - (font.getFontSize() / 1000)); - } - private float getStringWidth(String str, Font font) { - float wordWidth = 0; - float whitespaceWidth = font.getWidth(font.mapChar(' ')); - - for (int i = 0; i < str.length(); i++) { - float charWidth; - char c = str.charAt(i); - if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { - charWidth = font.getWidth(font.mapChar(c)); - if (charWidth <= 0) { - charWidth = whitespaceWidth; + private void addRelativePosition(Point2D relPos) { + if (getRunLength() > 0) { + if (relPos.getX() != 0) { + horizChanges++; + } + if (relPos.getY() != 0) { + vertChanges++; } - } else { - charWidth = whitespaceWidth; } - wordWidth += charWidth; + relativePositions.add(relPos); } - return wordWidth / 1000f; - } - private boolean hasUnsupportedGlyphs(String str, Font font) { - for (int i = 0; i < str.length(); i++) { - float charWidth; - char c = str.charAt(i); - if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { - if (!font.hasChar(c)) { - return true; - } + public void noteStartingTransformation(AffineTransform transform) { + if (textTransform == null) { + this.textTransform = new AffineTransform(transform); } } - return false; - } - /** - * Get the outline shape of the text characters. - * This uses the StrokingTextPainter to get the outline - * shape since in theory it should be the same. - * - * @param node the text node - * @return the outline shape of the text characters - */ - public Shape getOutline(TextNode node) { - return PROXY_PAINTER.getOutline(node); - } - - /** - * Get the bounds. - * This uses the StrokingTextPainter to get the bounds - * since in theory it should be the same. - * - * @param node the text node - * @return the bounds of the text - */ - public Rectangle2D getBounds2D(TextNode node) { - /* (todo) getBounds2D() is too slow - * because it uses the StrokingTextPainter. We should implement this - * method ourselves. */ - return PROXY_PAINTER.getBounds2D(node); - } - - /** - * Get the geometry bounds. - * This uses the StrokingTextPainter to get the bounds - * since in theory it should be the same. - * @param node the text node - * @return the bounds of the text - */ - public Rectangle2D getGeometryBounds(TextNode node) { - return PROXY_PAINTER.getGeometryBounds(node); - } - - // Methods that have no purpose for PS + public int getRunLength() { + return currentChars.length(); + } - /** - * Get the mark. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @param pos the position - * @param all select all - * @return null - */ - public Mark getMark(TextNode node, int pos, boolean all) { - return null; - } + private boolean isXShow() { + return vertChanges == 0; + } - /** - * Select at. - * This does nothing since the output is pdf and not interactive. - * @param x the x position - * @param y the y position - * @param node the text node - * @return null - */ - public Mark selectAt(double x, double y, TextNode node) { - return null; - } + private boolean isYShow() { + return horizChanges == 0; + } - /** - * Select to. - * This does nothing since the output is pdf and not interactive. - * @param x the x position - * @param y the y position - * @param beginMark the start mark - * @return null - */ - public Mark selectTo(double x, double y, Mark beginMark) { - return null; - } + public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi) + throws IOException { + if (getRunLength() > 0) { + if (log.isDebugEnabled()) { + log.debug("Text run: " + currentChars); + } + textUtil.writeTextMatrix(this.textTransform); + if (isXShow()) { + log.debug("Horizontal text: xshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, true, false); + } else if (isYShow()) { + log.debug("Vertical text: yshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, false, true); + } else { + log.debug("Arbitrary text: xyshow"); + paintXYShow(g2d, textUtil, tpi.fillPaint, true, true); + } + boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null); + if (stroke) { + log.debug("Stroked glyph outlines"); + paintStrokedGlyphs(g2d, textUtil, tpi.strokePaint, tpi.strokeStroke); + } + } + } - /** - * Selec first. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @return null - */ - public Mark selectFirst(TextNode node) { - return null; - } + private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint, + boolean x, boolean y) throws IOException { + PSGenerator gen = textUtil.gen; + char firstChar = this.currentChars.charAt(0); + //Font only has to be setup up before the first character + Font f = textUtil.selectFontForChar(firstChar); + char mapped = f.mapChar(firstChar); + textUtil.selectFont(f, mapped); + textUtil.setCurrentFont(f, mapped); + applyColor(paint, gen); + + StringBuffer sb = new StringBuffer(); + sb.append('('); + for (int i = 0, c = this.currentChars.length(); i < c; i++) { + char ch = this.currentChars.charAt(i); + mapped = f.mapChar(ch); + PSGenerator.escapeChar(mapped, sb); + } + sb.append(')'); + if (x || y) { + sb.append("\n["); + int idx = 0; + Iterator iter = this.relativePositions.iterator(); + while (iter.hasNext()) { + Point2D pt = (Point2D)iter.next(); + if (idx > 0) { + if (x) { + sb.append(format(gen, pt.getX())); + } + if (y) { + if (x) { + sb.append(' '); + } + sb.append(format(gen, -pt.getY())); + } + if (idx % 8 == 0) { + sb.append('\n'); + } else { + sb.append(' '); + } + } + idx++; + } + if (x) { + sb.append('0'); + } + if (y) { + if (x) { + sb.append(' '); + } + sb.append('0'); + } + sb.append(']'); + } + sb.append(' '); + if (x) { + sb.append('x'); + } + if (y) { + sb.append('y'); + } + sb.append("show"); // --> xshow, yshow or xyshow + gen.writeln(sb.toString()); + } - /** - * Select last. - * This does nothing since the output is pdf and not interactive. - * @param node the text node - * @return null - */ - public Mark selectLast(TextNode node) { - return null; - } + private String format(PSGenerator gen, double coord) { + if (Math.abs(coord) < 0.00001) { + return "0"; + } else { + return gen.formatDouble5(coord); + } + } - /** - * Get selected. - * This does nothing since the output is pdf and not interactive. - * @param start the start mark - * @param finish the finish mark - * @return null - */ - public int[] getSelected(Mark start, Mark finish) { - return null; - } + private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, + Paint strokePaint, Stroke stroke) throws IOException { + PSGenerator gen = textUtil.gen; + + applyColor(strokePaint, gen); + PSGraphics2D.applyStroke(stroke, gen); + + Font f = null; + Iterator iter = this.relativePositions.iterator(); + iter.next(); + Point2D pos = new Point2D.Double(0, 0); + gen.writeln("0 0 M"); + for (int i = 0, c = this.currentChars.length(); i < c; i++) { + char ch = this.currentChars.charAt(0); + if (i == 0) { + //Font only has to be setup up before the first character + f = textUtil.selectFontForChar(ch); + } + char mapped = f.mapChar(ch); + if (i == 0) { + textUtil.selectFont(f, mapped); + textUtil.setCurrentFont(f, mapped); + } + mapped = f.mapChar(this.currentChars.charAt(i)); + //add glyph outlines to current path + char codepoint = (char)(mapped % 256); + gen.write("(" + codepoint + ")"); + gen.writeln(" false charpath"); + + if (iter.hasNext()) { + //Position for the next character + Point2D pt = (Point2D)iter.next(); + pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); + gen.writeln(gen.formatDouble5(pos.getX()) + " " + + gen.formatDouble5(pos.getY()) + " M"); + } + } + gen.writeln("stroke"); //paints all accumulated glyph outlines + } - /** - * Get the highlighted shape. - * This does nothing since the output is pdf and not interactive. - * @param beginMark the start mark - * @param endMark the end mark - * @return null - */ - public Shape getHighlightShape(Mark beginMark, Mark endMark) { - return null; } } diff --git a/src/java/org/apache/fop/render/rtf/FOPRtfAttributes.java b/src/java/org/apache/fop/render/rtf/FOPRtfAttributes.java index 0cfa3e0fe..5cc752d30 100755 --- a/src/java/org/apache/fop/render/rtf/FOPRtfAttributes.java +++ b/src/java/org/apache/fop/render/rtf/FOPRtfAttributes.java @@ -21,8 +21,7 @@ package org.apache.fop.render.rtf; import java.awt.Color; import org.apache.fop.datatypes.Length; -import org.apache.fop.datatypes.PercentBaseContext; -import org.apache.fop.fo.FObj; +import org.apache.fop.render.DummyPercentBaseContext; import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfAttributes; import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfColorTable; @@ -62,7 +61,7 @@ public class FOPRtfAttributes extends RtfAttributes { */ public RtfAttributes setHalfPoints(String name, Length value) { //Convert millipoints to half-points - set(name, value.getValue(DummyPercentBaseContext.singleton) / (1000 / 2)); + set(name, value.getValue(DummyPercentBaseContext.getInstance()) / (1000 / 2)); return this; } @@ -82,17 +81,4 @@ public class FOPRtfAttributes extends RtfAttributes { return this; } - private static class DummyPercentBaseContext implements PercentBaseContext { - - static DummyPercentBaseContext singleton = new DummyPercentBaseContext(); - - private DummyPercentBaseContext() { - // noop - } - - public int getBaseLength(int lengthBase, FObj fo) { - return 0; - } - } - } diff --git a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java index ae4d67516..acb59ed7d 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java +++ b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java @@ -26,10 +26,12 @@ import org.apache.batik.bridge.Bridge; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.DocumentLoader; import org.apache.batik.bridge.UserAgent; -import org.apache.fop.fonts.FontInfo; + import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.fop.fonts.FontInfo; + /** * A FOP base implementation of a Batik BridgeContext. */ @@ -49,8 +51,6 @@ public abstract class AbstractFOPBridgeContext extends BridgeContext { * @param loader the Document Loader to use for referenced documents. * @param fontInfo the font list for the text painter, may be null * in which case text is painted as shapes - * @param linkTransform AffineTransform to properly place links, - * may be null * @param imageManager an image manager * @param imageSessionContext an image session context * @param linkTransform AffineTransform to properly place links, diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java index 53b8e2ad5..aec4126b4 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java +++ b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java @@ -19,13 +19,13 @@ package org.apache.fop.svg; +import org.w3c.dom.Element; + import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.SVGTextElementBridge; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.TextNode; import org.apache.batik.gvt.TextPainter; -import org.w3c.dom.Element; -import org.w3c.dom.Node; /** * Bridge class for the <text> element. @@ -65,49 +65,5 @@ public abstract class AbstractFOPTextElementBridge extends SVGTextElementBridge return node; } - /** - * Check if text element contains simple text. - * This checks the children of the text element to determine - * if the text is simple. The text is simple if it can be rendered - * with basic text drawing algorithms. This means there are no - * alternate characters, the font is known and there are no effects - * applied to the text. - * - * @param ctx the bridge context - * @param element the svg text element - * @param node the graphics node - * @return true if this text is simple of false if it cannot be - * easily rendered using normal drawString on the Graphics2D - */ - protected boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { - for (Node n = element.getFirstChild(); - n != null; - n = n.getNextSibling()) { - - switch (n.getNodeType()) { - case Node.ELEMENT_NODE: - - if (n.getLocalName().equals(SVG_TSPAN_TAG) - || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { - return false; - } else if (n.getLocalName().equals(SVG_TREF_TAG)) { - return false; - } - break; - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - default: - } - } - - /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { - return false; - }*/ - - return true; - } - } diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java index 83cfa8021..551da21df 100644 --- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java +++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java @@ -19,6 +19,19 @@ package org.apache.fop.svg; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.DOMImplementation; + +import org.xml.sax.EntityResolver; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfiguration; import org.apache.batik.bridge.UserAgent; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.dom.util.DocumentFactory; @@ -28,11 +41,16 @@ import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscodingHints; import org.apache.batik.transcoder.image.ImageTranscoder; import org.apache.batik.transcoder.keys.BooleanKey; +import org.apache.batik.transcoder.keys.FloatKey; +import org.apache.batik.util.ParsedURL; import org.apache.batik.util.SVGConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.impl.SimpleLog; -import org.w3c.dom.DOMImplementation; -import org.xml.sax.EntityResolver; + +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; /** * This is the common base class of all of FOP's transcoders. @@ -40,11 +58,24 @@ import org.xml.sax.EntityResolver; public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { /** + * The key is used to specify the resolution for on-the-fly images generated + * due to complex effects like gradients and filters. + */ + public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey(); + + /** * The key to specify whether to stroke text instead of using text * operations. */ public static final TranscodingHints.Key KEY_STROKE_TEXT = new BooleanKey(); + /** + * The key is used to specify whether the available fonts should be automatically + * detected. The alternative is to configure the transcoder manually using a configuration + * file. + */ + public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey(); + /** The value to turn on text stroking. */ public static final Boolean VALUE_FORMAT_ON = Boolean.TRUE; @@ -58,6 +89,9 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { private Log logger; private EntityResolver resolver; + private Configuration cfg = null; + private ImageManager imageManager; + private ImageSessionContext imageSessionContext; /** * Constructs a new FOP-style transcoder. @@ -80,7 +114,8 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { } /** - * @param logger + * Sets the logger. + * @param logger the logger */ public void setLogger(Log logger) { this.logger = logger; @@ -94,6 +129,43 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { this.resolver = resolver; } + /** {@inheritDoc} */ + public void configure(Configuration cfg) throws ConfigurationException { + this.cfg = cfg; + } + + /** + * Returns the default value for the KEY_AUTO_FONTS value. + * @return the default value + */ + protected boolean getAutoFontsDefault() { + return true; + } + + /** + * Returns the effective configuration for the transcoder. + * @return the effective configuration + */ + protected Configuration getEffectiveConfiguration() { + Configuration effCfg = this.cfg; + if (effCfg == null) { + //By default, enable font auto-detection if no cfg is given + boolean autoFonts = getAutoFontsDefault(); + if (hints.containsKey(KEY_AUTO_FONTS)) { + autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue(); + } + if (autoFonts) { + DefaultConfiguration c = new DefaultConfiguration("cfg"); + DefaultConfiguration fonts = new DefaultConfiguration("fonts"); + c.addChild(fonts); + DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect"); + fonts.addChild(autodetect); + effCfg = c; + } + } + return effCfg; + } + /** * Returns the logger associated with this transcoder. It returns a * SimpleLog if no logger has been explicitly set. @@ -142,6 +214,71 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder { return stroke; } + /** + * Returns the device resolution that has been set up. + * @return the device resolution (in dpi) + */ + protected float getDeviceResolution() { + if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { + return ((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue(); + } else { + return 72; + } + } + + /** + * Returns the ImageManager to be used by the transcoder. + * @return the image manager + */ + protected ImageManager getImageManager() { + return this.imageManager; + } + + /** + * Returns the ImageSessionContext to be used by the transcoder. + * @return the image session context + */ + protected ImageSessionContext getImageSessionContext() { + return this.imageSessionContext; + } + + /** + * Sets up the image infrastructure (the image loading framework). + * @param baseURI the base URI of the current document + */ + protected void setupImageInfrastructure(final String baseURI) { + final ImageContext imageContext = new ImageContext() { + public float getSourceResolution() { + return 25.4f / userAgent.getPixelUnitToMillimeter(); + } + }; + this.imageManager = new ImageManager(imageContext); + this.imageSessionContext = new AbstractImageSessionContext() { + + public ImageContext getParentContext() { + return imageContext; + } + + public float getTargetResolution() { + return getDeviceResolution(); + } + + public Source resolveURI(String uri) { + System.out.println("resolve " + uri); + try { + ParsedURL url = new ParsedURL(baseURI, uri); + InputStream in = url.openStream(); + StreamSource source = new StreamSource(in, url.toString()); + return source; + } catch (IOException ioe) { + userAgent.displayError(ioe); + return null; + } + } + + }; + } + // -------------------------------------------------------------------- // FOP's default error handler (for transcoders) // -------------------------------------------------------------------- diff --git a/src/java/org/apache/fop/svg/NativeTextPainter.java b/src/java/org/apache/fop/svg/NativeTextPainter.java new file mode 100644 index 000000000..7da7269c2 --- /dev/null +++ b/src/java/org/apache/fop/svg/NativeTextPainter.java @@ -0,0 +1,224 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.svg; + +import java.awt.Graphics2D; +import java.awt.font.TextAttribute; +import java.io.IOException; +import java.text.AttributedCharacterIterator; +import java.util.Iterator; +import java.util.List; + +import org.apache.batik.bridge.SVGFontFamily; +import org.apache.batik.gvt.font.GVTFont; +import org.apache.batik.gvt.font.GVTFontFamily; +import org.apache.batik.gvt.renderer.StrokingTextPainter; +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; +import org.apache.batik.gvt.text.TextSpanLayout; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.util.CharUtilities; + +/** + * Abstract base class for text painters that use specialized text commands native to an output + * format to render text. + */ +public abstract class NativeTextPainter extends StrokingTextPainter { + + /** the logger for this class */ + protected Log log = LogFactory.getLog(NativeTextPainter.class); + + /** the font collection */ + protected final FontInfo fontInfo; + + /** + * Creates a new instance. + * @param fontInfo the font collection + */ + public NativeTextPainter(FontInfo fontInfo) { + this.fontInfo = fontInfo; + } + + /** + * Indicates whether the given {@link Graphics2D} instance if compatible with this text painter + * implementation. + * @param g2d the instance to check + * @return true if the instance is compatible. + */ + protected abstract boolean isSupported(Graphics2D g2d); + + /** + * Paints a single text run. + * @param textRun the text run + * @param g2d the target Graphics2D instance + * @throws IOException if an I/O error occurs while rendering the text + */ + protected abstract void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException; + + /** {@inheritDoc} */ + protected void paintTextRuns(List textRuns, Graphics2D g2d) { + if (log.isTraceEnabled()) { + log.trace("paintTextRuns: count = " + textRuns.size()); + } + if (!isSupported(g2d)) { + super.paintTextRuns(textRuns, g2d); + return; + } + for (int i = 0; i < textRuns.size(); i++) { + TextRun textRun = (TextRun)textRuns.get(i); + try { + paintTextRun(textRun, g2d); + } catch (IOException ioe) { + //No other possibility than to use a RuntimeException + throw new RuntimeException(ioe); + } + } + } + + /** + * Finds an array of suitable fonts for a given AttributedCharacterIterator. + * @param aci the character iterator + * @return the array of fonts + */ + protected Font[] findFonts(AttributedCharacterIterator aci) { + List fonts = new java.util.ArrayList(); + List gvtFonts = (List) aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); + Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE); + Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT); + Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE); + + String style = ((posture != null) && (posture.floatValue() > 0.0)) + ? Font.STYLE_ITALIC : Font.STYLE_NORMAL; + int weight = ((taWeight != null) + && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD + : Font.WEIGHT_NORMAL; + + String firstFontFamily = null; + + //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES + //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set + /* The following code section is not available until Batik 1.7 is released. */ + GVTFont gvtFont = (GVTFont)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.GVT_FONT); + if (gvtFont != null) { + try { + String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6! + if (log.isDebugEnabled()) { + log.debug("Matching font family: " + gvtFontFamily); + } + if (fontInfo.hasFont(gvtFontFamily, style, weight)) { + FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style, + weight); + int fsize = (int)(fontSize.floatValue() * 1000); + fonts.add(fontInfo.getFontInstance(triplet, fsize)); + } + firstFontFamily = gvtFontFamily; + } catch (Exception e) { + //Most likely NoSuchMethodError here when using Batik 1.6 + //Just skip this section in this case + } + } + + if (gvtFonts != null) { + Iterator i = gvtFonts.iterator(); + while (i.hasNext()) { + GVTFontFamily fam = (GVTFontFamily) i.next(); + if (fam instanceof SVGFontFamily) { + return null; //Let Batik paint this text! + } + String fontFamily = fam.getFamilyName(); + if (log.isDebugEnabled()) { + log.debug("Matching font family: " + fontFamily); + } + if (fontInfo.hasFont(fontFamily, style, weight)) { + FontTriplet triplet = fontInfo.fontLookup(fontFamily, style, + weight); + int fsize = (int)(fontSize.floatValue() * 1000); + fonts.add(fontInfo.getFontInstance(triplet, fsize)); + } + if (firstFontFamily == null) { + firstFontFamily = fontFamily; + } + } + } + if (fonts.size() == 0) { + if (firstFontFamily == null) { + //This will probably never happen. Just to be on the safe side. + firstFontFamily = "any"; + } + //lookup with fallback possibility (incl. substitution notification) + FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight); + int fsize = (int)(fontSize.floatValue() * 1000); + fonts.add(fontInfo.getFontInstance(triplet, fsize)); + } + return (Font[])fonts.toArray(new Font[fonts.size()]); + } + + /** + * Collects all characters from an {@link AttributedCharacterIterator}. + * @param runaci the character iterator + * @return the characters + */ + protected CharSequence collectCharacters(AttributedCharacterIterator runaci) { + StringBuffer chars = new StringBuffer(); + for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) { + chars.append(runaci.current()); + runaci.next(); + } + return chars; + } + + protected final void logTextRun(AttributedCharacterIterator runaci, TextSpanLayout layout) { + if (log.isTraceEnabled()) { + int charCount = runaci.getEndIndex() - runaci.getBeginIndex(); + log.trace("================================================"); + log.trace("New text run:"); + log.trace("char count: " + charCount); + log.trace("range: " + + runaci.getBeginIndex() + " - " + runaci.getEndIndex()); + log.trace("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs() + } + } + + protected final void logCharacter(char ch, TextSpanLayout layout, int index, + boolean visibleChar) { + if (log.isTraceEnabled()) { + log.trace("glyph " + index + + " -> " + layout.getGlyphIndex(index) + " => " + ch); + if (CharUtilities.isAnySpace(ch) && ch != 32) { + log.trace("Space found: " + Integer.toHexString(ch)); + } else if (ch == CharUtilities.ZERO_WIDTH_JOINER) { + log.trace("ZWJ found: " + Integer.toHexString(ch)); + } else if (ch == CharUtilities.SOFT_HYPHEN) { + log.trace("Soft hyphen found: " + Integer.toHexString(ch)); + } + if (!visibleChar) { + log.trace("Invisible glyph found: " + Integer.toHexString(ch)); + } + } + } + + +} diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java index 364c7a6f3..e8569f881 100644 --- a/src/java/org/apache/fop/svg/PDFBridgeContext.java +++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java @@ -26,10 +26,12 @@ import org.apache.batik.bridge.DocumentLoader; import org.apache.batik.bridge.SVGTextElementBridge; import org.apache.batik.bridge.UserAgent; import org.apache.batik.gvt.TextPainter; -import org.apache.fop.fonts.FontInfo; + import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.fop.fonts.FontInfo; + /** * BridgeContext which registers the custom bridges for PDF output. */ @@ -38,11 +40,9 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext { /** * Constructs a new bridge context. * @param userAgent the user agent - * @param loader the Document Loader to use for referenced documents. + * @param documentLoader the Document Loader to use for referenced documents. * @param fontInfo the font list for the text painter, may be null * in which case text is painted as shapes - * @param linkTransform AffineTransform to properly place links, - * may be null * @param imageManager an image manager * @param imageSessionContext an image session context * @param linkTransform AffineTransform to properly place links, @@ -52,7 +52,8 @@ public class PDFBridgeContext extends AbstractFOPBridgeContext { FontInfo fontInfo, ImageManager imageManager, ImageSessionContext imageSessionContext, AffineTransform linkTransform) { - super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform); + super(userAgent, documentLoader, fontInfo, + imageManager, imageSessionContext, linkTransform); } /** diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java index e101a9573..b77518ab0 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java @@ -55,6 +55,22 @@ public class PDFDocumentGraphics2DConfigurator { //Fonts try { + FontInfo fontInfo = createFontInfo(cfg); + graphics.setFontInfo(fontInfo); + } catch (FOPException e) { + throw new ConfigurationException("Error while setting up fonts", e); + } + } + + /** + * Creates the {@link FontInfo} instance for the given configuration. + * @param cfg the configuration + * @return the font collection + * @throws FOPException if an error occurs while setting up the fonts + */ + public static FontInfo createFontInfo(Configuration cfg) throws FOPException { + FontInfo fontInfo = new FontInfo(); + if (cfg != null) { FontResolver fontResolver = FontManager.createMinimalFontResolver(); //TODO The following could be optimized by retaining the FontManager somewhere FontManager fontManager = new FontManager(); @@ -73,12 +89,11 @@ public class PDFDocumentGraphics2DConfigurator { if (fontManager.useCache()) { fontManager.getFontCache().save(); } - FontInfo fontInfo = new FontInfo(); FontSetup.setup(fontInfo, fontInfoList, fontResolver); - graphics.setFontInfo(fontInfo); - } catch (FOPException e) { - throw new ConfigurationException("Error while setting up fonts", e); + } else { + FontSetup.setup(fontInfo); } + return fontInfo; } } diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index ede368f58..c5da8af6f 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -707,8 +707,13 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand if (s == null) { return; } - preparePainting(); PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); + if (iter.isDone()) { + // no segments available. Not worth doing anything + return; + } + preparePainting(); + processPathIterator(iter); // clip area currentStream.write("W\n"); diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java index 85447a4f9..dddf61a6e 100644 --- a/src/java/org/apache/fop/svg/PDFTextPainter.java +++ b/src/java/org/apache/fop/svg/PDFTextPainter.java @@ -25,28 +25,18 @@ import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; -import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; -import java.lang.reflect.Method; import java.text.AttributedCharacterIterator; -import java.util.Iterator; -import java.util.List; -import org.apache.batik.bridge.SVGFontFamily; -import org.apache.batik.gvt.font.GVTFont; -import org.apache.batik.gvt.font.GVTFontFamily; import org.apache.batik.gvt.font.GVTGlyphVector; -import org.apache.batik.gvt.renderer.StrokingTextPainter; -import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; import org.apache.batik.gvt.text.TextPaintInfo; import org.apache.batik.gvt.text.TextSpanLayout; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontTriplet; import org.apache.fop.util.CharUtilities; /** @@ -59,193 +49,159 @@ import org.apache.fop.util.CharUtilities; * * @version $Id$ */ -public class PDFTextPainter extends StrokingTextPainter { +class PDFTextPainter extends NativeTextPainter { private static final boolean DEBUG = false; - private final boolean strokeText = false; - private final FontInfo fontInfo; - /** * Create a new PDF text painter with the given font information. * @param fi the font info */ public PDFTextPainter(FontInfo fi) { - fontInfo = fi; + super(fi); } /** {@inheritDoc} */ - protected void paintTextRuns(List textRuns, Graphics2D g2d) { - if (DEBUG) { - System.out.println("paintTextRuns: count = " + textRuns.size()); - //fontInfo.dumpAllTripletsToSystemOut(); - } - if (!(g2d instanceof PDFGraphics2D) || strokeText) { - super.paintTextRuns(textRuns, g2d); + protected boolean isSupported(Graphics2D g2d) { + return g2d instanceof PDFGraphics2D; + } + + /** {@inheritDoc} */ + protected void paintTextRun(TextRun textRun, Graphics2D g2d) { + AttributedCharacterIterator runaci = textRun.getACI(); + runaci.first(); + + TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); + if (tpi == null || !tpi.visible) { return; } + if ((tpi != null) && (tpi.composite != null)) { + g2d.setComposite(tpi.composite); + } + + //------------------------------------ + TextSpanLayout layout = textRun.getLayout(); + logTextRun(runaci, layout); + CharSequence chars = collectCharacters(runaci); + runaci.first(); //Reset ACI + final PDFGraphics2D pdf = (PDFGraphics2D)g2d; PDFTextUtil textUtil = new PDFTextUtil(pdf.fontInfo) { protected void write(String code) { pdf.currentStream.write(code); } }; - for (int i = 0; i < textRuns.size(); i++) { - TextRun textRun = (TextRun)textRuns.get(i); - AttributedCharacterIterator runaci = textRun.getACI(); - runaci.first(); - TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); - if (tpi == null || !tpi.visible) { - continue; - } - if ((tpi != null) && (tpi.composite != null)) { - g2d.setComposite(tpi.composite); - } + if (DEBUG) { + log.debug("Text: " + chars); + pdf.currentStream.write("%Text: " + chars + "\n"); + } - //------------------------------------ - TextSpanLayout layout = textRun.getLayout(); - if (DEBUG) { - int charCount = runaci.getEndIndex() - runaci.getBeginIndex(); - System.out.println("================================================"); - System.out.println("New text run:"); - System.out.println("char count: " + charCount); - System.out.println("range: " - + runaci.getBeginIndex() + " - " + runaci.getEndIndex()); - System.out.println("glyph count: " + layout.getGlyphCount()); //=getNumGlyphs() - } - //Gather all characters of the run - StringBuffer chars = new StringBuffer(); - for (runaci.first(); runaci.getIndex() < runaci.getEndIndex();) { - chars.append(runaci.current()); - runaci.next(); - } - runaci.first(); - if (DEBUG) { - System.out.println("Text: " + chars); - pdf.currentStream.write("%Text: " + chars + "\n"); - } + GeneralPath debugShapes = null; + if (DEBUG) { + debugShapes = new GeneralPath(); + } - GeneralPath debugShapes = null; - if (DEBUG) { - debugShapes = new GeneralPath(); - } + Font[] fonts = findFonts(runaci); + if (fonts == null || fonts.length == 0) { + //Draw using Java2D when no native fonts are available + textRun.getLayout().draw(g2d); + return; + } - Font[] fonts = findFonts(runaci); - if (fonts == null || fonts.length == 0) { - //Draw using Java2D - textRun.getLayout().draw(g2d); + textUtil.saveGraphicsState(); + textUtil.concatMatrix(g2d.getTransform()); + Shape imclip = g2d.getClip(); + pdf.writeClip(imclip); + + applyColorAndPaint(tpi, pdf); + + textUtil.beginTextObject(); + textUtil.setFonts(fonts); + boolean stroke = (tpi.strokePaint != null) + && (tpi.strokeStroke != null); + textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false); + + AffineTransform localTransform = new AffineTransform(); + Point2D prevPos = null; + double prevVisibleCharWidth = 0.0; + GVTGlyphVector gv = layout.getGlyphVector(); + for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { + char ch = chars.charAt(index); + boolean visibleChar = gv.isGlyphVisible(index) + || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); + logCharacter(ch, layout, index, visibleChar); + if (!visibleChar) { continue; } + Point2D glyphPos = gv.getGlyphPosition(index); - textUtil.saveGraphicsState(); - textUtil.concatMatrix(g2d.getTransform()); - Shape imclip = g2d.getClip(); - pdf.writeClip(imclip); - - applyColorAndPaint(tpi, pdf); - - textUtil.beginTextObject(); - textUtil.setFonts(fonts); - boolean stroke = (tpi.strokePaint != null) - && (tpi.strokeStroke != null); - textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false); - - AffineTransform localTransform = new AffineTransform(); - Point2D prevPos = null; - double prevVisibleCharWidth = 0.0; - GVTGlyphVector gv = layout.getGlyphVector(); - for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { - char ch = chars.charAt(index); - boolean visibleChar = gv.isGlyphVisible(index) - || (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); - if (DEBUG) { - System.out.println("glyph " + index - + " -> " + layout.getGlyphIndex(index) + " => " + ch); - if (CharUtilities.isAnySpace(ch) && ch != 32) { - System.out.println("Space found: " + Integer.toHexString(ch)); - } - if (ch == CharUtilities.ZERO_WIDTH_JOINER) { - System.out.println("ZWJ found: " + Integer.toHexString(ch)); - } - if (ch == CharUtilities.SOFT_HYPHEN) { - System.out.println("Soft hyphen found: " + Integer.toHexString(ch)); - } - if (!visibleChar) { - System.out.println("Invisible glyph found: " + Integer.toHexString(ch)); - } - } - if (!visibleChar) { - continue; - } - Point2D p = gv.getGlyphPosition(index); - - AffineTransform glyphTransform = gv.getGlyphTransform(index); - //TODO Glyph transforms could be refined so not every char has to be painted - //with its own TJ command (stretch/squeeze case could be optimized) - if (DEBUG) { - System.out.println("pos " + p + ", transform " + glyphTransform); - Shape sh; - sh = gv.getGlyphLogicalBounds(index); - if (sh == null) { - sh = new Ellipse2D.Double(p.getX(), p.getY(), 2, 2); - } - debugShapes.append(sh, false); + AffineTransform glyphTransform = gv.getGlyphTransform(index); + //TODO Glyph transforms could be refined so not every char has to be painted + //with its own TJ command (stretch/squeeze case could be optimized) + if (log.isTraceEnabled()) { + log.trace("pos " + glyphPos + ", transform " + glyphTransform); + } + if (DEBUG) { + Shape sh = gv.getGlyphLogicalBounds(index); + if (sh == null) { + sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); } + debugShapes.append(sh, false); + } - //Exact position of the glyph - localTransform.setToIdentity(); - localTransform.translate(p.getX(), p.getY()); - if (glyphTransform != null) { - localTransform.concatenate(glyphTransform); - } - localTransform.scale(1, -1); + //Exact position of the glyph + localTransform.setToIdentity(); + localTransform.translate(glyphPos.getX(), glyphPos.getY()); + if (glyphTransform != null) { + localTransform.concatenate(glyphTransform); + } + localTransform.scale(1, -1); - boolean yPosChanged = (prevPos == null - || prevPos.getY() != p.getY() - || glyphTransform != null); - if (yPosChanged) { - if (index > 0) { - textUtil.writeTJ(); - textUtil.writeTextMatrix(localTransform); - } - } else { - double xdiff = p.getX() - prevPos.getX(); - //Width of previous character - Font font = textUtil.getCurrentFont(); - double cw = prevVisibleCharWidth; - double effxdiff = (1000 * xdiff) - cw; - if (effxdiff != 0) { - double adjust = (-effxdiff / font.getFontSize()); - textUtil.adjustGlyphTJ(adjust * 1000); - } - if (DEBUG) { - System.out.println("==> x diff: " + xdiff + ", " + effxdiff - + ", charWidth: " + cw); - } - } - Font f = textUtil.selectFontForChar(ch); - if (f != textUtil.getCurrentFont()) { + boolean yPosChanged = (prevPos == null + || prevPos.getY() != glyphPos.getY() + || glyphTransform != null); + if (yPosChanged) { + if (index > 0) { textUtil.writeTJ(); - textUtil.setCurrentFont(f); - textUtil.writeTf(f); textUtil.writeTextMatrix(localTransform); } - char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); - textUtil.writeTJChar(paintChar); - - //Update last position - prevPos = p; - prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index)); + } else { + double xdiff = glyphPos.getX() - prevPos.getX(); + //Width of previous character + Font font = textUtil.getCurrentFont(); + double cw = prevVisibleCharWidth; + double effxdiff = (1000 * xdiff) - cw; + if (effxdiff != 0) { + double adjust = (-effxdiff / font.getFontSize()); + textUtil.adjustGlyphTJ(adjust * 1000); + } + if (log.isTraceEnabled()) { + log.trace("==> x diff: " + xdiff + ", " + effxdiff + + ", charWidth: " + cw); + } } - textUtil.writeTJ(); - textUtil.endTextObject(); - textUtil.restoreGraphicsState(); - if (DEBUG) { - g2d.setStroke(new BasicStroke(0)); - g2d.setColor(Color.LIGHT_GRAY); - g2d.draw(debugShapes); + Font f = textUtil.selectFontForChar(ch); + if (f != textUtil.getCurrentFont()) { + textUtil.writeTJ(); + textUtil.setCurrentFont(f); + textUtil.writeTf(f); + textUtil.writeTextMatrix(localTransform); } + char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); + textUtil.writeTJChar(paintChar); + + //Update last position + prevPos = glyphPos; + prevVisibleCharWidth = textUtil.getCurrentFont().getCharWidth(chars.charAt(index)); + } + textUtil.writeTJ(); + textUtil.endTextObject(); + textUtil.restoreGraphicsState(); + if (DEBUG) { + g2d.setStroke(new BasicStroke(0)); + g2d.setColor(Color.LIGHT_GRAY); + g2d.draw(debugShapes); } } @@ -271,85 +227,4 @@ public class PDFTextPainter extends StrokingTextPainter { pdf.applyAlpha(fillAlpha, PDFGraphics2D.OPAQUE); } - private Font[] findFonts(AttributedCharacterIterator aci) { - List fonts = new java.util.ArrayList(); - List gvtFonts = (List) aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); - Float posture = (Float) aci.getAttribute(TextAttribute.POSTURE); - Float taWeight = (Float) aci.getAttribute(TextAttribute.WEIGHT); - Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE); - - String style = ((posture != null) && (posture.floatValue() > 0.0)) - ? Font.STYLE_ITALIC : Font.STYLE_NORMAL; - int weight = ((taWeight != null) - && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD - : Font.WEIGHT_NORMAL; - - String firstFontFamily = null; - - //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES - //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set - /* The following code section is not available until Batik 1.7 is released. */ - GVTFont gvtFont = (GVTFont)aci.getAttribute( - GVTAttributedCharacterIterator.TextAttribute.GVT_FONT); - if (gvtFont != null) { - try { - Method method = gvtFont.getClass().getMethod("getFamilyName", null); - String gvtFontFamily = (String)method.invoke(gvtFont, null); - //TODO Uncomment the following line when Batik 1.7 is shipped with FOP - //String gvtFontFamily = gvtFont.getFamilyName(); //Not available in Batik 1.6 - if (DEBUG) { - System.out.print(gvtFontFamily + ", "); - } - if (fontInfo.hasFont(gvtFontFamily, style, weight)) { - FontTriplet triplet = fontInfo.fontLookup(gvtFontFamily, style, - weight); - int fsize = (int)(fontSize.floatValue() * 1000); - fonts.add(fontInfo.getFontInstance(triplet, fsize)); - } - firstFontFamily = gvtFontFamily; - } catch (Exception e) { - //Most likely NoSuchMethodError here when using Batik 1.6 - //Just skip this section in this case - } - } - - if (gvtFonts != null) { - Iterator i = gvtFonts.iterator(); - while (i.hasNext()) { - GVTFontFamily fam = (GVTFontFamily) i.next(); - if (fam instanceof SVGFontFamily) { - return null; //Let Batik paint this text! - } - String fontFamily = fam.getFamilyName(); - if (DEBUG) { - System.out.print(fontFamily + ", "); - } - if (fontInfo.hasFont(fontFamily, style, weight)) { - FontTriplet triplet = fontInfo.fontLookup(fontFamily, style, - weight); - int fsize = (int)(fontSize.floatValue() * 1000); - fonts.add(fontInfo.getFontInstance(triplet, fsize)); - } - if (firstFontFamily == null) { - firstFontFamily = fontFamily; - } - } - } - if (fonts.size() == 0) { - if (firstFontFamily == null) { - //This will probably never happen. Just to be on the safe side. - firstFontFamily = "any"; - } - //lookup with fallback possibility (incl. substitution notification) - FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight); - int fsize = (int)(fontSize.floatValue() * 1000); - fonts.add(fontInfo.getFontInstance(triplet, fsize)); - } - if (DEBUG) { - System.out.println(); - } - return (Font[])fonts.toArray(new Font[fonts.size()]); - } - }
\ No newline at end of file diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java index 333cd5e4c..062270f6b 100644 --- a/src/java/org/apache/fop/svg/PDFTranscoder.java +++ b/src/java/org/apache/fop/svg/PDFTranscoder.java @@ -23,34 +23,19 @@ import java.awt.Color; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.InputStream; - -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.svg.SVGLength; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; -import org.apache.avalon.framework.configuration.ConfigurationException; -import org.apache.avalon.framework.configuration.DefaultConfiguration; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.UnitProcessor; import org.apache.batik.bridge.UserAgent; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderOutput; -import org.apache.batik.transcoder.TranscodingHints; import org.apache.batik.transcoder.image.ImageTranscoder; -import org.apache.batik.transcoder.keys.BooleanKey; -import org.apache.batik.transcoder.keys.FloatKey; -import org.apache.batik.util.ParsedURL; - -import org.apache.xmlgraphics.image.loader.ImageContext; -import org.apache.xmlgraphics.image.loader.ImageManager; -import org.apache.xmlgraphics.image.loader.ImageSessionContext; -import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; import org.apache.fop.Version; import org.apache.fop.fonts.FontInfo; @@ -91,27 +76,9 @@ import org.apache.fop.fonts.FontInfo; public class PDFTranscoder extends AbstractFOPTranscoder implements Configurable { - /** - * The key is used to specify the resolution for on-the-fly images generated - * due to complex effects like gradients and filters. - */ - public static final TranscodingHints.Key KEY_DEVICE_RESOLUTION = new FloatKey(); - - /** - * The key is used to specify whether the available fonts should be automatically - * detected. The alternative is to configure the transcoder manually using a configuration - * file. - */ - public static final TranscodingHints.Key KEY_AUTO_FONTS = new BooleanKey(); - - private Configuration cfg = null; - /** Graphics2D instance that is used to paint to */ protected PDFDocumentGraphics2D graphics = null; - private ImageManager imageManager; - private ImageSessionContext imageSessionContext; - /** * Constructs a new <tt>PDFTranscoder</tt>. */ @@ -133,11 +100,6 @@ public class PDFTranscoder extends AbstractFOPTranscoder }; } - /** {@inheritDoc} */ - public void configure(Configuration cfg) throws ConfigurationException { - this.cfg = cfg; - } - /** * Transcodes the specified Document as an image in the specified output. * @@ -155,28 +117,13 @@ public class PDFTranscoder extends AbstractFOPTranscoder + Version.getVersion() + ": PDF Transcoder for Batik"); if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { - graphics.setDeviceDPI(((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue()); + graphics.setDeviceDPI(getDeviceResolution()); } setupImageInfrastructure(uri); try { - Configuration effCfg = this.cfg; - if (effCfg == null) { - //By default, enable font auto-detection if no cfg is given - boolean autoFonts = true; - if (hints.containsKey(KEY_AUTO_FONTS)) { - autoFonts = ((Boolean)hints.get(KEY_AUTO_FONTS)).booleanValue(); - } - if (autoFonts) { - DefaultConfiguration c = new DefaultConfiguration("pdf-transcoder"); - DefaultConfiguration fonts = new DefaultConfiguration("fonts"); - c.addChild(fonts); - DefaultConfiguration autodetect = new DefaultConfiguration("auto-detect"); - fonts.addChild(autodetect); - effCfg = c; - } - } + Configuration effCfg = getEffectiveConfiguration(); if (effCfg != null) { PDFDocumentGraphics2DConfigurator configurator @@ -242,39 +189,6 @@ public class PDFTranscoder extends AbstractFOPTranscoder } } - private void setupImageInfrastructure(final String baseURI) { - final ImageContext imageContext = new ImageContext() { - public float getSourceResolution() { - return 25.4f / userAgent.getPixelUnitToMillimeter(); - } - }; - this.imageManager = new ImageManager(imageContext); - this.imageSessionContext = new AbstractImageSessionContext() { - - public ImageContext getParentContext() { - return imageContext; - } - - public float getTargetResolution() { - return graphics.getDeviceDPI(); - } - - public Source resolveURI(String uri) { - System.out.println("resolve " + uri); - try { - ParsedURL url = new ParsedURL(baseURI, uri); - InputStream in = url.openStream(); - StreamSource source = new StreamSource(in, url.toString()); - return source; - } catch (IOException ioe) { - userAgent.displayError(ioe); - return null; - } - } - - }; - } - /** {@inheritDoc} */ protected BridgeContext createBridgeContext() { //For compatibility with Batik 1.6 @@ -288,7 +202,7 @@ public class PDFTranscoder extends AbstractFOPTranscoder fontInfo = null; } BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo, - this.imageManager, this.imageSessionContext); + getImageManager(), getImageSessionContext()); return ctx; } diff --git a/src/java/org/apache/fop/tools/anttasks/SerializeHyphPattern.java b/src/java/org/apache/fop/tools/anttasks/SerializeHyphPattern.java deleted file mode 100644 index 778c39f9a..000000000 --- a/src/java/org/apache/fop/tools/anttasks/SerializeHyphPattern.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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. - */ - -/* $Id$ */ - -package org.apache.fop.tools.anttasks; - -// Java -import java.io.File; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.List; - -// Ant -import org.apache.tools.ant.Task; -import org.apache.tools.ant.DirectoryScanner; -import org.apache.tools.ant.types.FileSet; - -// FOP -import org.apache.fop.hyphenation.HyphenationTree; -import org.apache.fop.hyphenation.HyphenationException; - -/** - * SerializeHyphPattern - */ - - -public class SerializeHyphPattern extends Task { - private List filesets = new java.util.ArrayList(); - private File targetDir; - private boolean errorDump = false; - - /** - * {@inheritDoc} - */ - public void execute() throws org.apache.tools.ant.BuildException { - // deal with the filesets - for (int i = 0; i < getFilesets().size(); i++) { - FileSet fs = (FileSet) getFilesets().get(i); - DirectoryScanner ds = fs.getDirectoryScanner(getProject()); - File basedir = ds.getBasedir(); - String[] files = ds.getIncludedFiles(); - for (int j = 0; j < files.length; j++) { - processFile(basedir, files[j].substring(0, files[j].length() - 4)); - } - } - } // end execute - - - /** - * Adds a set of pattern files (nested fileset attribute). - * @param set a fileset - */ - public void addFileset(FileSet set) { - filesets.add(set); - } - - /** - * Returns the current list of filesets. - * @return the filesets - */ - public List getFilesets() { - return this.filesets; - } - - /** - * Sets the target directory - * @param targetDir target directory - */ - public void setTargetDir(String targetDir) { - File dir = new File(targetDir); - this.targetDir = dir; - } - - /** - * Controls the amount of error information dumped. - * @param errorDump True if more error info should be provided - */ - public void setErrorDump(boolean errorDump) { - this.errorDump = errorDump; - } - - - /* - * checks whether input or output files exists or the latter is older than input file - * and start build if necessary - */ - private void processFile(File basedir, String filename) { - File infile = new File(basedir, filename + ".xml"); - File outfile = new File(targetDir, filename + ".hyp"); - //long outfileLastModified = outfile.lastModified(); - boolean startProcess = true; - - startProcess = rebuild(infile, outfile); - if (startProcess) { - buildPatternFile(infile, outfile); - } - } - - /* - * serializes pattern files - */ - private void buildPatternFile(File infile, File outfile) { - System.out.println("Processing " + infile); - HyphenationTree hTree = new HyphenationTree(); - try { - hTree.loadPatterns(infile.toString()); - if (errorDump) { - System.out.println("Stats: "); - hTree.printStats(); - } - } catch (HyphenationException ex) { - System.err.println("Can't load patterns from xml file " + infile - + " - Maybe hyphenation.dtd is missing?"); - if (errorDump) { - System.err.println(ex.toString()); - } - } - // serialize class - try { - ObjectOutputStream out = new ObjectOutputStream( - new java.io.BufferedOutputStream( - new java.io.FileOutputStream(outfile))); - out.writeObject(hTree); - out.close(); - } catch (IOException ioe) { - System.err.println("Can't write compiled pattern file: " - + outfile); - System.err.println(ioe); - } - } - - /** - * Checks for existence of output file and compares - * dates with input and stylesheet file - */ - private boolean rebuild(File infile, File outfile) { - if (outfile.exists()) { - // checks whether output file is older than input file - if (outfile.lastModified() < infile.lastModified()) { - return true; - } - } else { - // if output file does not exist, start process - return true; - } - return false; - } // end rebuild - - /* - * //quick access for debugging - * public static void main (String args[]) { - * SerializeHyphPattern ser = new SerializeHyphPattern(); - * FileSet set = new FileSet(); - * set.setDir(new File("src/hyph")); - * set.setIncludes("*.xml"); - * ser.addFileset(set); - * ser.setTargetDir("build/hyph"); - * ser.execute(); - * } - */ - - -} diff --git a/src/java/org/apache/fop/util/ColorExt.java b/src/java/org/apache/fop/util/ColorExt.java index d2e73d227..22c2dcc36 100644 --- a/src/java/org/apache/fop/util/ColorExt.java +++ b/src/java/org/apache/fop/util/ColorExt.java @@ -176,7 +176,9 @@ public final class ColorExt extends Color { sb.append(this.rgbReplacementGreen + ","); sb.append(this.rgbReplacementBlue + ","); sb.append(this.iccProfileName + ","); - sb.append("\"" + this.iccProfileSrc + "\""); + if (this.iccProfileSrc != null) { + sb.append("\"" + this.iccProfileSrc + "\""); + } float[] colorComponents = this.getColorComponents(null); for (int ix = 0; ix < colorComponents.length; ix++) { sb.append(","); diff --git a/src/java/org/apache/fop/util/ColorUtil.java b/src/java/org/apache/fop/util/ColorUtil.java index 9534bfba3..8100aef96 100644 --- a/src/java/org/apache/fop/util/ColorUtil.java +++ b/src/java/org/apache/fop/util/ColorUtil.java @@ -38,6 +38,9 @@ import org.apache.fop.fo.expr.PropertyException; */ public final class ColorUtil { + /** The name for the uncalibrated CMYK pseudo-profile */ + public static final String CMYK_PSEUDO_PROFILE = "#CMYK"; + /** * * keeps all the predefined and parsed colors. @@ -319,26 +322,32 @@ public final class ColorUtil { if (iccProfileName == null || "".equals(iccProfileName)) { throw new PropertyException("ICC profile name missing"); } - /* Get and verify ICC profile source */ - String iccProfileSrc = args[4].trim(); - if (iccProfileSrc == null || "".equals(iccProfileSrc)) { - throw new PropertyException("ICC profile source missing"); - } - if (iccProfileSrc.startsWith("\"") || iccProfileSrc.startsWith("'")) { - iccProfileSrc = iccProfileSrc.substring(1); - } - if (iccProfileSrc.endsWith("\"") || iccProfileSrc.endsWith("'")) { - iccProfileSrc = iccProfileSrc.substring(0, iccProfileSrc.length() - 1); + ColorSpace colorSpace = null; + String iccProfileSrc = null; + if (isPseudoProfile(iccProfileName)) { + if (CMYK_PSEUDO_PROFILE.equalsIgnoreCase(iccProfileName)) { + colorSpace = CMYKColorSpace.getInstance(); + } else { + assert false : "Incomplete implementation"; + } + } else { + /* Get and verify ICC profile source */ + iccProfileSrc = args[4].trim(); + if (iccProfileSrc == null || "".equals(iccProfileSrc)) { + throw new PropertyException("ICC profile source missing"); + } + if (iccProfileSrc.startsWith("\"") || iccProfileSrc.startsWith("'")) { + iccProfileSrc = iccProfileSrc.substring(1); + } + if (iccProfileSrc.endsWith("\"") || iccProfileSrc.endsWith("'")) { + iccProfileSrc = iccProfileSrc.substring(0, iccProfileSrc.length() - 1); + } } /* ICC profile arguments */ float[] iccComponents = new float[args.length - 5]; for (int ix = 4; ++ix < args.length;) { iccComponents[ix - 5] = Float.parseFloat(args[ix].trim()); } - /* Ask FOP factory to get ColorSpace for the specified ICC profile source */ - ColorSpace colorSpace = (foUserAgent != null - ? foUserAgent.getFactory().getColorSpace( - foUserAgent.getBaseURL(), iccProfileSrc) : null); float red = 0, green = 0, blue = 0; red = Float.parseFloat(args[0].trim()); @@ -352,6 +361,11 @@ public final class ColorUtil { + "Fallback RGB arguments to fop-rgb-icc() must be [0..1]"); } + /* Ask FOP factory to get ColorSpace for the specified ICC profile source */ + if (foUserAgent != null && iccProfileSrc != null) { + colorSpace = foUserAgent.getFactory().getColorSpace( + foUserAgent.getBaseURL(), iccProfileSrc); + } if (colorSpace != null) { // ColorSpace available - create ColorExt (keeps track of replacement rgb // values for possible later colorTOsRGBString call @@ -440,7 +454,7 @@ public final class ColorUtil { CMYKColorSpace cmykCs = CMYKColorSpace.getInstance(); float[] rgb = cmykCs.toRGB(cmyk); parsedColor = ColorExt.createFromFoRgbIcc(rgb[0], rgb[1], rgb[2], - null, "#CMYK", cmykCs, cmyk); + CMYK_PSEUDO_PROFILE, null, cmykCs, cmyk); } catch (PropertyException pe) { throw pe; } catch (Exception e) { @@ -465,13 +479,13 @@ public final class ColorUtil { */ public static String colorToString(Color color) { ColorSpace cs = color.getColorSpace(); - if (cs != null && cs.getType() == ColorSpace.TYPE_CMYK) { + if (color instanceof ColorExt) { + return ((ColorExt)color).toFunctionCall(); + } else if (cs != null && cs.getType() == ColorSpace.TYPE_CMYK) { StringBuffer sbuf = new StringBuffer(24); float[] cmyk = color.getColorComponents(null); sbuf.append("cmyk(" + cmyk[0] + "," + cmyk[1] + "," + cmyk[2] + "," + cmyk[3] + ")"); return sbuf.toString(); - } else if (color instanceof ColorExt) { - return ((ColorExt)color).toFunctionCall(); } else { StringBuffer sbuf = new StringBuffer(); sbuf.append('#'); @@ -681,4 +695,35 @@ public final class ColorUtil { return new Color(cols[0], cols[1], cols[2], cols[3]); } + /** + * Indicates whether the given color profile name is one of the pseudo-profiles supported + * by FOP (ex. #CMYK). + * @param colorProfileName the color profile name to check + * @return true if the color profile name is of a built-in pseudo-profile + */ + public static boolean isPseudoProfile(String colorProfileName) { + return CMYK_PSEUDO_PROFILE.equalsIgnoreCase(colorProfileName); + } + + /** + * Indicates whether the color is a gray value. + * @param col the color + * @return true if it is a gray value + */ + public static boolean isGray(Color col) { + return (col.getRed() == col.getBlue() && col.getRed() == col.getGreen()); + } + + /** + * Creates an uncalibrary CMYK color with the given gray value. + * @param black the gray component (0 - 1) + * @return the CMYK color + */ + public static Color toCMYKGrayColor(float black) { + float[] cmyk = new float[] {0f, 0f, 0f, 1.0f - black}; + CMYKColorSpace cmykCs = CMYKColorSpace.getInstance(); + float[] rgb = cmykCs.toRGB(cmyk); + return ColorExt.createFromFoRgbIcc(rgb[0], rgb[1], rgb[2], + CMYK_PSEUDO_PROFILE, null, cmykCs, cmyk); + } } diff --git a/src/java/org/apache/fop/util/DOM2SAX.java b/src/java/org/apache/fop/util/DOM2SAX.java index b9021ed3a..39d2af4a1 100644 --- a/src/java/org/apache/fop/util/DOM2SAX.java +++ b/src/java/org/apache/fop/util/DOM2SAX.java @@ -26,7 +26,6 @@ import java.util.Stack; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; - import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; @@ -77,7 +76,7 @@ public class DOM2SAX { contentHandler.endDocument(); } } - + /** * Writes the given fragment using the given ContentHandler. * @param node DOM node diff --git a/src/java/org/apache/fop/util/bitmap/DitherUtil.java b/src/java/org/apache/fop/util/bitmap/DitherUtil.java new file mode 100644 index 000000000..c61befc9c --- /dev/null +++ b/src/java/org/apache/fop/util/bitmap/DitherUtil.java @@ -0,0 +1,153 @@ +/* + * 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. + */ + +/* $Id$ */ + +package org.apache.fop.util.bitmap; + +import java.awt.Color; + +/** + * Utility methods for dithering. + */ +public class DitherUtil { + + /** Selects a 2x2 Bayer dither matrix (5 grayscales) */ + public static final int DITHER_MATRIX_2X2 = 2; + /** Selects a 4x4 Bayer dither matrix (17 grayscales) */ + public static final int DITHER_MATRIX_4X4 = 4; + /** Selects a 8x8 Bayer dither matrix (65 grayscales) */ + public static final int DITHER_MATRIX_8X8 = 8; + + //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix) + private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1}; + private static final int[] BAYER_D4; + private static final int[] BAYER_D8; + + static { + BAYER_D4 = deriveBayerMatrix(BAYER_D2); + BAYER_D8 = deriveBayerMatrix(BAYER_D4); + } + + private static int[] deriveBayerMatrix(int[] d) { + int[] dn = new int[d.length * 4]; + int half = (int)Math.sqrt(d.length); + for (int part = 0; part < 4; part++) { + for (int i = 0, c = d.length; i < c; i++) { + setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]); + } + } + return dn; + } + + private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) { + int xoff = (part & 1) * half; + int yoff = (part & 2) * half * half; + int matrixIndex = yoff + ((idx / half) * half * 2) + (idx % half) + xoff; + dn[matrixIndex] = value; + } + + /** + * Returns the Bayer dither base pattern for a particular matrix size. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @return the base pattern for the given size + */ + public static int[] getBayerBasePattern(int matrix) { + int[] result = new int[matrix * matrix]; + switch (matrix) { + case DITHER_MATRIX_2X2: + System.arraycopy(BAYER_D2, 0, result, 0, BAYER_D2.length); + break; + case DITHER_MATRIX_4X4: + System.arraycopy(BAYER_D4, 0, result, 0, BAYER_D4.length); + break; + case DITHER_MATRIX_8X8: + System.arraycopy(BAYER_D8, 0, result, 0, BAYER_D8.length); + break; + default: + throw new IllegalArgumentException("Unsupported dither matrix: " + matrix); + } + return result; + } + + /** + * Returns a byte array containing the dither pattern for the given 8-bit gray value. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @param gray255 the gray value (0-255) + * @param doubleMatrix true if the 4x4 matrix shall be doubled to a 8x8 + * @return the dither pattern + */ + public static byte[] getBayerDither(int matrix, int gray255, boolean doubleMatrix) { + int ditherIndex; + byte[] dither; + int[] bayer; + switch (matrix) { + case DITHER_MATRIX_4X4: + ditherIndex = gray255 * 17 / 255; + bayer = BAYER_D4; + break; + case DITHER_MATRIX_8X8: + ditherIndex = gray255 * 65 / 255; + bayer = BAYER_D8; + break; + default: + throw new IllegalArgumentException("Unsupported dither matrix: " + matrix); + } + if (doubleMatrix) { + if (doubleMatrix && (matrix != DITHER_MATRIX_4X4)) { + throw new IllegalArgumentException("doubleMatrix=true is only allowed for 4x4"); + } + dither = new byte[bayer.length / 8 * 4]; + for (int i = 0, c = bayer.length; i < c; i++) { + boolean dot = !(bayer[i] < ditherIndex - 1); + if (dot) { + int byteIdx = i / 4; + dither[byteIdx] |= 1 << (i % 4); + dither[byteIdx] |= 1 << ((i % 4) + 4); + dither[byteIdx + 4] |= 1 << (i % 4); + dither[byteIdx + 4] |= 1 << ((i % 4) + 4); + } + } + } else { + dither = new byte[bayer.length / 8]; + for (int i = 0, c = bayer.length; i < c; i++) { + boolean dot = !(bayer[i] < ditherIndex - 1); + if (dot) { + int byteIdx = i / 8; + dither[byteIdx] |= 1 << (i % 8); + } + } + } + return dither; + } + + /** + * Returns a byte array containing the dither pattern for the given 8-bit gray value. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @param col the color + * @param doubleMatrix true if the 4x4 matrix shall be doubled to a 8x8 + * @return the dither pattern + */ + public static byte[] getBayerDither(int matrix, Color col, boolean doubleMatrix) { + float black = BitmapImageUtil.convertToGray(col.getRGB()) / 256f; + return getBayerDither(matrix, Math.round(black * 256), doubleMatrix); + } + +} |