diff options
Diffstat (limited to 'src/java/org/apache/fop')
26 files changed, 659 insertions, 433 deletions
diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index a10692890..9612d4ae5 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -120,6 +120,9 @@ public class FOUserAgent { /** @see #setBreakIndentInheritanceOnReferenceAreaBoundary(boolean) */ private boolean breakIndentInheritanceOnReferenceAreaBoundary = false; + /** Allows enabling kerning on the base 14 fonts, default is false */ + private boolean enableBase14Kerning = false; + /* Additional fo.ElementMapping subclasses set by user */ private List additionalElementMappings = null; @@ -240,6 +243,19 @@ public class FOUserAgent { this.breakIndentInheritanceOnReferenceAreaBoundary = value; } + /** @return true if kerning on base 14 fonts is enabled */ + public boolean isBase14KerningEnabled() { + return this.enableBase14Kerning; + } + + /** + * Controls whether kerning is activated on base 14 fonts. + * @param value true if kerning should be activated + */ + public void setBase14KerningEnabled(boolean value) { + this.enableBase14Kerning = value; + } + /** * Sets an explicit LayoutManagerMaker instance which overrides the one * defined by the AreaTreeHandler. diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index e4f1292d2..e3a291eee 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -19,6 +19,7 @@ package org.apache.fop.area; import java.awt.geom.Rectangle2D; +import java.util.List; import java.util.Map; import java.util.Stack; import java.util.StringTokenizer; @@ -674,10 +675,27 @@ public class AreaTreeParser { private class WordMaker extends AbstractMaker { + private int[] toIntArray(String s) { + if (s == null || s.length() == 0) { + return null; + } + StringTokenizer tokenizer = new StringTokenizer(s, " "); + List values = new java.util.ArrayList(); + while (tokenizer.hasMoreTokens()) { + values.add(new Integer(tokenizer.nextToken())); + } + int[] res = new int[values.size()]; + for (int i = 0, c = res.length; i < c; i++) { + res[i] = ((Integer)values.get(i)).intValue(); + } + return res; + } + public void endElement() { int offset = getAttributeAsInteger(lastAttributes, "offset", 0); + int[] letterAdjust = toIntArray(lastAttributes.getValue("letter-adjust")); String txt = content.toString(); - WordArea word = new WordArea(txt, offset); + WordArea word = new WordArea(txt, offset, letterAdjust); AbstractTextArea text = getCurrentText(); word.setParentArea(text); text.addChildArea(word); @@ -691,7 +709,8 @@ public class AreaTreeParser { String txt = content.toString(); //TODO the isAdjustable parameter is currently not used/implemented if (txt.length() > 0) { - SpaceArea space = new SpaceArea(txt.charAt(0), offset, false); + boolean adjustable = getAttributeAsBoolean(lastAttributes, "adj", true); + SpaceArea space = new SpaceArea(txt.charAt(0), offset, adjustable); AbstractTextArea text = getCurrentText(); space.setParentArea(text); text.addChildArea(space); diff --git a/src/java/org/apache/fop/area/inline/Character.java b/src/java/org/apache/fop/area/inline/Character.java index f672e3632..04f73aa86 100644 --- a/src/java/org/apache/fop/area/inline/Character.java +++ b/src/java/org/apache/fop/area/inline/Character.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ package org.apache.fop.area.inline; /** * Single character inline area. * This inline area holds a single character. + * @deprecated A TextArea with a single WordArea as its child should be used instead. */ public class Character extends AbstractTextArea { // use a String instead of a character because if this character diff --git a/src/java/org/apache/fop/area/inline/SpaceArea.java b/src/java/org/apache/fop/area/inline/SpaceArea.java index 6ec573107..193553464 100644 --- a/src/java/org/apache/fop/area/inline/SpaceArea.java +++ b/src/java/org/apache/fop/area/inline/SpaceArea.java @@ -28,11 +28,6 @@ public class SpaceArea extends InlineArea { protected String space;
/**
- * The correction offset for the next area
- */
- protected int offset = 0;
-
- /**
* Is this space adjustable?
*/
protected boolean isAdjustable;
@@ -56,16 +51,8 @@ public class SpaceArea extends InlineArea { return new String(space);
}
- /**
- * @return Returns the offset.
- */
- public int getOffset() {
- return offset;
- }
- /**
- * @param o The offset to set.
- */
- public void setOffset(int o) {
- offset = o;
+ /** @return true if the space is adjustable (WRT word-space processing) */
+ public boolean isAdjustable() {
+ return this.isAdjustable;
}
}
diff --git a/src/java/org/apache/fop/area/inline/TextArea.java b/src/java/org/apache/fop/area/inline/TextArea.java index 3bee4254f..a3962f457 100644 --- a/src/java/org/apache/fop/area/inline/TextArea.java +++ b/src/java/org/apache/fop/area/inline/TextArea.java @@ -54,7 +54,17 @@ public class TextArea extends AbstractTextArea { * @param offset the offset for the next area */ public void addWord(String word, int offset) { - WordArea wordArea = new WordArea(word, offset); + addWord(word, offset, null); + } + + /** + * Create and add a WordArea child to this TextArea. + * + * @param word the word string + * @param offset the offset for the next area + */ + public void addWord(String word, int offset, int[] letterAdjust) { + WordArea wordArea = new WordArea(word, offset, letterAdjust); addChildArea(wordArea); wordArea.setParentArea(this); } diff --git a/src/java/org/apache/fop/area/inline/WordArea.java b/src/java/org/apache/fop/area/inline/WordArea.java index b998b819e..f62dc499f 100644 --- a/src/java/org/apache/fop/area/inline/WordArea.java +++ b/src/java/org/apache/fop/area/inline/WordArea.java @@ -22,24 +22,25 @@ package org.apache.fop.area.inline; */
public class WordArea extends InlineArea {
- /**
- * The text for this word area
- */
+ /** The text for this word area */
protected String word;
- /**
- * The correction offset for the next area
- */
+ /** The correction offset for the next area */
protected int offset = 0;
+
+ /** An array of width for adjusting the individual letters (optional) */
+ protected int[] letterAdjust;
/**
* Create a word area
* @param w the word string
* @param o the offset for the next area
+ * @param la the letter adjust array (may be null)
*/
- public WordArea(String w, int o) {
+ public WordArea(String w, int o, int[] la) {
word = w;
offset = o;
+ this.letterAdjust = la;
}
/**
@@ -61,4 +62,10 @@ public class WordArea extends InlineArea { public void setOffset(int o) {
offset = o;
}
+
+ /** @return the array of letter adjust widths */
+ public int[] getLetterAdjustArray() {
+ return this.letterAdjust;
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java index f39bb108d..85c7da1ee 100644 --- a/src/java/org/apache/fop/fonts/CustomFont.java +++ b/src/java/org/apache/fop/fonts/CustomFont.java @@ -21,9 +21,6 @@ package org.apache.fop.fonts; import java.util.Map; import javax.xml.transform.Source; -import org.apache.fop.apps.FOUserAgent; - - /** * Abstract base class for custom fonts loaded from files, for example. */ @@ -33,7 +30,7 @@ public abstract class CustomFont extends Typeface private String fontName = null; private String embedFileName = null; private String embedResourceName = null; - private FOUserAgent userAgent = null; + private FontResolver resolver = null; private int capHeight = 0; private int xHeight = 0; @@ -48,7 +45,7 @@ public abstract class CustomFont extends Typeface private int firstChar = 0; private int lastChar = 255; - private Map kerning = new java.util.HashMap(); + private Map kerning; private boolean useKerning = true; @@ -74,10 +71,11 @@ public abstract class CustomFont extends Typeface * @return Source for an embeddable font file or null if not available. */ public Source getEmbedFileSource() { - if (userAgent != null && embedFileName != null) { - return userAgent.resolveURI(embedFileName, userAgent.getFontBaseURL()); + if (resolver != null && embedFileName != null) { + return resolver.resolve(embedFileName); + } else { + return null; } - return null; } /** @@ -212,14 +210,14 @@ public abstract class CustomFont extends Typeface * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() */ public final boolean hasKerningInfo() { - return (isKerningEnabled() & kerning.isEmpty()); + return (isKerningEnabled() && (kerning != null) && !kerning.isEmpty()); } /** * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() */ public final Map getKerningInfo() { - if (isKerningEnabled()) { + if (hasKerningInfo()) { return kerning; } else { return java.util.Collections.EMPTY_MAP; @@ -343,17 +341,20 @@ public abstract class CustomFont extends Typeface } /** - * Sets the user agent environment. Needed for URI resolution - * @param userAgent the user agent + * Sets the font resolver. Needed for URI resolution. + * @param resolver the font resolver */ - public void setUserAgent(FOUserAgent userAgent) { - this.userAgent = userAgent; + public void setResolver(FontResolver resolver) { + this.resolver = resolver; } /** * @see org.apache.fop.fonts.MutableFont#putKerningEntry(Integer, Map) */ public void putKerningEntry(Integer key, Map value) { + if (kerning == null) { + kerning = new java.util.HashMap(); + } this.kerning.put(key, value); } diff --git a/src/java/org/apache/fop/fonts/Font.java b/src/java/org/apache/fop/fonts/Font.java index cbbc81b21..1a2d39651 100644 --- a/src/java/org/apache/fop/fonts/Font.java +++ b/src/java/org/apache/fop/fonts/Font.java @@ -111,18 +111,39 @@ public class Font { return metric.getXHeight(fontSize) / 1000; } + /** @return true if the font has kerning info */ + public boolean hasKerning() { + return metric.hasKerningInfo(); + } + /** * Returns the font's kerning table * @return the kerning table */ public Map getKerning() { - Map ret = metric.getKerningInfo(); - if (ret != null) { - return ret; + if (metric.hasKerningInfo()) { + return metric.getKerningInfo(); } else { return java.util.Collections.EMPTY_MAP; } } + + /** + * Returns the amount of kerning between two characters. + * @param ch1 first character + * @param ch2 second character + * @return the distance to adjust for kerning, 0 if there's no kerning + */ + public int getKernValue(char ch1, char ch2) { + Map kernPair = (Map)getKerning().get(new Integer(ch1)); + if (kernPair != null) { + Integer width = (Integer)kernPair.get(new Integer(ch2)); + if (width != null) { + return width.intValue(); + } + } + return 0; + } /** * Returns the width of a character @@ -206,63 +227,50 @@ public class Font { if ((c == '\n') || (c == '\r') || (c == '\t') || (c == '\u00A0')) { width = getCharWidth(' '); } else { - width = getWidth(mapChar(c)); + if (hasChar(c)) { + width = getWidth(mapChar(c)); + } else { + width = -1; + } if (width <= 0) { // Estimate the width of spaces not represented in // the font - int em = getWidth(mapChar('m')); - int en = getWidth(mapChar('n')); - if (em <= 0) { - em = 500 * getFontSize(); - } - if (en <= 0) { - en = em - 10; - } + int em = getFontSize(); //http://en.wikipedia.org/wiki/Em_(typography) + int en = em / 2; //http://en.wikipedia.org/wiki/En_(typography) if (c == ' ') { width = em; - } - if (c == '\u2000') { + } else if (c == '\u2000') { width = en; - } - if (c == '\u2001') { + } else if (c == '\u2001') { width = em; - } - if (c == '\u2002') { + } else if (c == '\u2002') { width = em / 2; - } - if (c == '\u2003') { + } else if (c == '\u2003') { width = getFontSize(); - } - if (c == '\u2004') { + } else if (c == '\u2004') { width = em / 3; - } - if (c == '\u2005') { + } else if (c == '\u2005') { width = em / 4; - } - if (c == '\u2006') { + } else if (c == '\u2006') { width = em / 6; - } - if (c == '\u2007') { - width = getCharWidth(' '); - } - if (c == '\u2008') { + } else if (c == '\u2007') { + width = getCharWidth('0'); + } else if (c == '\u2008') { width = getCharWidth('.'); - } - if (c == '\u2009') { + } else if (c == '\u2009') { width = em / 5; - } - if (c == '\u200A') { - width = 5; - } - if (c == '\u200B') { - width = 100; - } - if (c == '\u202F') { + } else if (c == '\u200A') { + width = em / 10; + } else if (c == '\u200B') { + width = 0; + } else if (c == '\u202F') { width = getCharWidth(' ') / 2; - } - if (c == '\u3000') { + } else if (c == '\u3000') { width = getCharWidth(' ') * 2; + } else { + //Will be internally replaced by "#" if not found + width = getWidth(mapChar(c)); } } } diff --git a/src/java/org/apache/fop/fonts/FontReader.java b/src/java/org/apache/fop/fonts/FontReader.java index 74ed06579..cc9888962 100644 --- a/src/java/org/apache/fop/fonts/FontReader.java +++ b/src/java/org/apache/fop/fonts/FontReader.java @@ -34,7 +34,6 @@ import org.xml.sax.helpers.DefaultHandler; //FOP import org.apache.fop.apps.FOPException; -import org.apache.fop.apps.FOUserAgent; import org.xml.sax.InputSource; /** @@ -114,11 +113,11 @@ public class FontReader extends DefaultHandler { } /** - * Sets the user agent environment. Needed for URI resolution - * @param userAgent the user agent + * Sets the font resolver. Needed for URI resolution. + * @param resolver the font resolver */ - public void setUserAgent(FOUserAgent userAgent) { - returnFont.setUserAgent(userAgent); + public void setResolver(FontResolver resolver) { + returnFont.setResolver(resolver); } diff --git a/src/java/org/apache/fop/fonts/FontResolver.java b/src/java/org/apache/fop/fonts/FontResolver.java new file mode 100644 index 000000000..7999de646 --- /dev/null +++ b/src/java/org/apache/fop/fonts/FontResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fonts; + +import javax.xml.transform.Source; + +/** + * This interface is used to resolve absolute and relative font URIs. + */ +public interface FontResolver { + + /** + * Called to resolve an URI to a Source instance. The base URI needed by the URIResolver's + * resolve() method is defined to be implicitely available in this case. If the URI cannot + * be resolved, null is returned and it is assumed that the FontResolver implementation + * already warned the user about the problem. + * @param href An href attribute, which may be relative or absolute. + * @return A Source object, or null if the href could not resolved. + */ + Source resolve(String href); + +} diff --git a/src/java/org/apache/fop/fonts/FontSetup.java b/src/java/org/apache/fop/fonts/FontSetup.java index 140fcb8b8..4cab92d9d 100644 --- a/src/java/org/apache/fop/fonts/FontSetup.java +++ b/src/java/org/apache/fop/fonts/FontSetup.java @@ -18,8 +18,6 @@ package org.apache.fop.fonts; -import org.apache.fop.apps.FOUserAgent; - // FOP (base 14 fonts) import org.apache.fop.fonts.base14.Helvetica; import org.apache.fop.fonts.base14.HelveticaBold; @@ -68,22 +66,39 @@ public class FontSetup { * triplets for lookup. * * @param fontInfo the font info object to set up - * @param embedList ??? + * @param embedList a list of EmbedFontInfo objects + * @param resolver the font resolver + */ + public static void setup(FontInfo fontInfo, List embedList, FontResolver resolver) { + setup(fontInfo, embedList, resolver, false); + } + + /** + * Sets up the font info object. + * + * Adds metrics for basic fonts and useful family-style-weight + * triplets for lookup. + * + * @param fontInfo the font info object to set up + * @param embedList a list of EmbedFontInfo objects + * @param resolver the font resolver + * @param enableBase14Kerning true if kerning should be enabled for base 14 fonts */ - public static void setup(FontInfo fontInfo, List embedList, FOUserAgent ua) { + public static void setup(FontInfo fontInfo, List embedList, FontResolver resolver, + boolean enableBase14Kerning) { - fontInfo.addMetrics("F1", new Helvetica()); - fontInfo.addMetrics("F2", new HelveticaOblique()); - fontInfo.addMetrics("F3", new HelveticaBold()); - fontInfo.addMetrics("F4", new HelveticaBoldOblique()); - fontInfo.addMetrics("F5", new TimesRoman()); - fontInfo.addMetrics("F6", new TimesItalic()); - fontInfo.addMetrics("F7", new TimesBold()); - fontInfo.addMetrics("F8", new TimesBoldItalic()); - fontInfo.addMetrics("F9", new Courier()); - fontInfo.addMetrics("F10", new CourierOblique()); - fontInfo.addMetrics("F11", new CourierBold()); - fontInfo.addMetrics("F12", new CourierBoldOblique()); + fontInfo.addMetrics("F1", new Helvetica(enableBase14Kerning)); + fontInfo.addMetrics("F2", new HelveticaOblique(enableBase14Kerning)); + fontInfo.addMetrics("F3", new HelveticaBold(enableBase14Kerning)); + fontInfo.addMetrics("F4", new HelveticaBoldOblique(enableBase14Kerning)); + fontInfo.addMetrics("F5", new TimesRoman(enableBase14Kerning)); + fontInfo.addMetrics("F6", new TimesItalic(enableBase14Kerning)); + fontInfo.addMetrics("F7", new TimesBold(enableBase14Kerning)); + fontInfo.addMetrics("F8", new TimesBoldItalic(enableBase14Kerning)); + fontInfo.addMetrics("F9", new Courier(enableBase14Kerning)); + fontInfo.addMetrics("F10", new CourierOblique(enableBase14Kerning)); + fontInfo.addMetrics("F11", new CourierBold(enableBase14Kerning)); + fontInfo.addMetrics("F12", new CourierBoldOblique(enableBase14Kerning)); fontInfo.addMetrics("F13", new Symbol()); fontInfo.addMetrics("F14", new ZapfDingbats()); @@ -162,17 +177,18 @@ public class FontSetup { "normal", Font.NORMAL); /* Add configured fonts */ - addConfiguredFonts(fontInfo, embedList, 15, ua); + addConfiguredFonts(fontInfo, embedList, 15, resolver); } /** * Add fonts from configuration file starting with internal name F<num>. * @param fontInfo the font info object to set up - * @param fontInfoList + * @param fontInfoList a list of EmbedFontInfo objects * @param num starting index for internal font numbering + * @param resolver the font resolver */ public static void addConfiguredFonts(FontInfo fontInfo, List fontInfoList - , int num, FOUserAgent userAgent) { + , int num, FontResolver resolver) { if (fontInfoList == null) { return; //No fonts to process } @@ -196,7 +212,7 @@ public class FontSetup { LazyFont font = new LazyFont(configFontInfo.getEmbedFile(), metricsFile, configFontInfo.getKerning(), - userAgent); + resolver); fontInfo.addMetrics(internalName, font); List triplets = configFontInfo.getFontTriplets(); diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java index f8d22f3bb..ee0b47ec3 100644 --- a/src/java/org/apache/fop/fonts/LazyFont.java +++ b/src/java/org/apache/fop/fonts/LazyFont.java @@ -17,10 +17,8 @@ /* $Id$ */ package org.apache.fop.fonts; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import javax.xml.transform.Source; @@ -29,7 +27,6 @@ import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.FOPException; -import org.apache.fop.apps.FOUserAgent; import org.xml.sax.InputSource; /** @@ -47,21 +44,21 @@ public class LazyFont extends Typeface implements FontDescriptor { private Typeface realFont = null; private FontDescriptor realFontDescriptor = null; - private FOUserAgent userAgent = null; + private FontResolver resolver = null; /** * Main constructor * @param fontEmbedPath path to embeddable file (may be null) * @param metricsFileName path to the metrics XML file * @param useKerning True, if kerning should be enabled - * @param userAgent the environment for uri resoltuion + * @param resolver the font resolver to handle font URIs */ public LazyFont(String fontEmbedPath, String metricsFileName - , boolean useKerning, FOUserAgent userAgent) { + , boolean useKerning, FontResolver resolver) { this.metricsFileName = metricsFileName; this.fontEmbedPath = fontEmbedPath; this.useKerning = useKerning; - this.userAgent = userAgent; + this.resolver = resolver; } private void load(boolean fail) { @@ -69,9 +66,8 @@ public class LazyFont extends Typeface implements FontDescriptor { try { /**@todo Possible thread problem here */ FontReader reader = null; - if (userAgent != null) { - Source source = userAgent.resolveURI(metricsFileName - , userAgent.getFontBaseURL()); + if (resolver != null) { + Source source = resolver.resolve(metricsFileName); if (source == null) { String err = "Cannot load font: failed to create Source from metrics file " + metricsFileName; @@ -106,7 +102,7 @@ public class LazyFont extends Typeface implements FontDescriptor { } reader.setKerningEnabled(useKerning); reader.setFontEmbedPath(fontEmbedPath); - reader.setUserAgent(userAgent); + reader.setResolver(resolver); realFont = reader.getFont(); if (realFont instanceof FontDescriptor) { realFontDescriptor = (FontDescriptor) realFont; diff --git a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java index 302e82c26..a0e198ad5 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java @@ -68,14 +68,15 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { hyphIPD = font.getCharWidth(fobj.getCommonHyphenation().hyphenationCharacter); borderProps = fobj.getCommonBorderPaddingBackground(); setCommonBorderPaddingBackground(borderProps); - org.apache.fop.area.inline.Character chArea = getCharacterInlineArea(fobj); + org.apache.fop.area.inline.TextArea chArea = getCharacterInlineArea(fobj); chArea.setBaselineOffset(font.getAscender()); setCurrentArea(chArea); } - private org.apache.fop.area.inline.Character getCharacterInlineArea(Character node) { - org.apache.fop.area.inline.Character ch - = new org.apache.fop.area.inline.Character(node.getCharacter()); + private org.apache.fop.area.inline.TextArea getCharacterInlineArea(Character node) { + org.apache.fop.area.inline.TextArea ch + = new org.apache.fop.area.inline.TextArea(); + ch.addWord(String.valueOf(node.getCharacter()), 0); TraitSetter.setProducerID(ch, node.getId()); TraitSetter.addTextDecoration(ch, fobj.getTextDecoration()); return ch; diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java index ac8867b31..7522db6a3 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,14 +62,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private short iLScount; private MinOptMax ipdArea; private boolean bHyphenated; + private boolean isSpace; public AreaInfo(short iSIndex, short iBIndex, short iWS, short iLS, - MinOptMax ipd, boolean bHyph) { + MinOptMax ipd, boolean bHyph, boolean isSpace) { iStartIndex = iSIndex; iBreakIndex = iBIndex; iWScount = iWS; iLScount = iLS; ipdArea = ipd; bHyphenated = bHyph; + this.isSpace = isSpace; } public String toString() { @@ -79,6 +81,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { + ", sidx=" + iStartIndex + ", bidx=" + iBreakIndex + ", hyph=" + bHyphenated + + ", space=" + isSpace + "]"; } @@ -104,14 +107,12 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private FOText foText; private char[] textArray; + /** Contains an array of widths to adjust for kerning and letter spacing */ + private MinOptMax[] letterAdjustArray; //size = textArray.length + 1 + /** The sum of all entries in the letterAdjustArray */ + private MinOptMax totalLetterAdjust = new MinOptMax(0); private static final char NEWLINE = '\n'; - private static final char SPACE = '\u0020'; // Normal space - private static final char NBSPACE = '\u00A0'; // Non-breaking space - private static final char LINEBREAK = '\u2028'; - private static final char ZERO_WIDTH_SPACE = '\u200B'; - // byte order mark - private static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF'; private Font font = null; /** Start index of first character in this parent Area */ @@ -128,8 +129,12 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private MinOptMax letterSpaceIPD; /** size of the hyphen character glyph in current font */ private int hyphIPD; + /** 1/1 of word-spacing value */ + private SpaceVal ws; /** 1/2 of word-spacing value */ private SpaceVal halfWS; + /** 1/2 of letter-spacing value */ + private SpaceVal halfLS; /** Number of space characters after previous possible break position. */ private int iNbSpacesPending; @@ -156,6 +161,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { textArray = new char[node.endIndex - node.startIndex]; System.arraycopy(node.ca, node.startIndex, textArray, 0, node.endIndex - node.startIndex); + letterAdjustArray = new MinOptMax[textArray.length + 1]; vecAreaInfo = new java.util.ArrayList(); } @@ -168,9 +174,13 @@ public class TextLayoutManager extends LeafNodeLayoutManager { spaceCharIPD = font.getCharWidth(' '); // Use hyphenationChar property hyphIPD = font.getCharWidth(foText.getCommonHyphenation().hyphenationCharacter); - // Make half-space: <space> on either side of a word-space) + SpaceVal ls = SpaceVal.makeLetterSpacing(foText.getLetterSpacing()); - SpaceVal ws = SpaceVal.makeWordSpacing(foText.getWordSpacing(), ls, font); + halfLS = new SpaceVal(MinOptMax.multiply(ls.getSpace(), 0.5), + ls.isConditional(), ls.isForcing(), ls.getPrecedence()); + + ws = SpaceVal.makeWordSpacing(foText.getWordSpacing(), ls, font); + // Make half-space: <space> on either side of a word-space) halfWS = new SpaceVal(MinOptMax.multiply(ws.getSpace(), 0.5), ws.isConditional(), ws.isForcing(), ws.getPrecedence()); @@ -180,6 +190,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T // there is no letter space after the last character of a word, // nor after a space character + // NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing // set letter space and word space dimension; // the default value "normal" was converted into a MinOptMax value @@ -288,13 +299,22 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } if (ai == null) { return; - } else if (ai.iLScount == ai.iBreakIndex - ai.iStartIndex + } + int textLength = ai.iBreakIndex - ai.iStartIndex; + if (ai.iLScount == textLength && context.isLastArea()) { // the line ends at a character like "/" or "-"; // remove the letter space after the last character realWidth.add(MinOptMax.multiply(letterSpaceIPD, -1)); iLScount--; } + + for (int i = ai.iStartIndex + 1; i < ai.iBreakIndex + 1; i++) { + MinOptMax ladj = letterAdjustArray[i]; + if (ladj != null && ladj.isElastic()) { + iLScount++; + } + } // add hyphenation character if the last word is hyphenated if (context.isLastArea() && ai.bHyphenated) { @@ -415,9 +435,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // set the text of the TextArea, split into words and spaces int wordStartIndex = -1; AreaInfo areaInfo; - for (int i = firstIndex; i <= lastIndex; i ++) { + for (int i = firstIndex; i <= lastIndex; i++) { areaInfo = (AreaInfo) vecAreaInfo.get(i); - if (areaInfo.iWScount > 0) { + if (areaInfo.isSpace) { // areaInfo stores information about a space // add a space to the TextArea char spaceChar = textArray[areaInfo.iStartIndex]; @@ -429,17 +449,28 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // here starts a new word wordStartIndex = areaInfo.iStartIndex; } - if (i == lastIndex || ((AreaInfo) vecAreaInfo.get(i + 1)).iWScount > 0) { + if (i == lastIndex || ((AreaInfo) vecAreaInfo.get(i + 1)).isSpace) { // here ends a new word // add a word to the TextArea - String wordChars = new String(textArray, wordStartIndex, areaInfo.iBreakIndex - wordStartIndex); + int len = areaInfo.iBreakIndex - wordStartIndex; + String wordChars = new String(textArray, wordStartIndex, len); if (isLastArea && i == lastIndex && areaInfo.bHyphenated) { // add the hyphenation character wordChars += foText.getCommonHyphenation().hyphenationCharacter; } - textArea.addWord(wordChars, 0); + int[] letterAdjust = new int[wordChars.length()]; + int lsCount = areaInfo.iLScount; + for (int letter = 0; letter < len; letter++) { + MinOptMax adj = letterAdjustArray[letter + wordStartIndex + 1]; + letterAdjust[letter] = (adj != null ? adj.opt : 0); + if (lsCount > 0) { + letterAdjust[letter] += textArea.getTextLetterSpaceAdjust(); + lsCount--; + } + } + textArea.addWord(wordChars, 0, letterAdjust); wordStartIndex = -1; } } @@ -451,7 +482,40 @@ public class TextLayoutManager extends LeafNodeLayoutManager { return textArea; } + + private void addToLetterAdjust(int index, int width) { + if (letterAdjustArray[index] == null) { + letterAdjustArray[index] = new MinOptMax(width); + } else { + letterAdjustArray[index].add(width); + } + totalLetterAdjust.add(width); + } + private void addToLetterAdjust(int index, MinOptMax width) { + if (letterAdjustArray[index] == null) { + letterAdjustArray[index] = new MinOptMax(width); + } else { + letterAdjustArray[index].add(width); + } + totalLetterAdjust.add(width); + } + + /** + * Indicates whether a character is a space in terms of this layout manager. + * @param ch the character + * @return true if it's a space + */ + private static boolean isSpace(final char ch) { + return ch == CharUtilities.SPACE + || ch == CharUtilities.NBSPACE + || CharUtilities.isFixedWidthSpace(ch); + } + + private static boolean isBreakChar(final char ch) { + return (BREAK_CHARS.indexOf(ch) >= 0); + } + /** @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(LayoutContext, int) */ public LinkedList getNextKnuthElements(LayoutContext context, int alignment) { lineStartBAP = context.getLineStartBorderAndPaddingWidth(); @@ -464,13 +528,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager { returnList.add(sequence); while (iNextStart < textArray.length) { - if (textArray[iNextStart] == SPACE - || textArray[iNextStart] == NBSPACE) { + char ch = textArray[iNextStart]; + if (ch == CharUtilities.SPACE + || ch == CharUtilities.NBSPACE) { // normal space or non-breaking space: // create the AreaInfo object ai = new AreaInfo(iNextStart, (short) (iNextStart + 1), (short) 1, (short) 0, - wordSpaceIPD, false); + wordSpaceIPD, false, true); vecAreaInfo.add(ai); // create the elements @@ -479,7 +544,21 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // advance to the next character iNextStart++; - } else if (textArray[iNextStart] == NEWLINE) { + } else if (CharUtilities.isFixedWidthSpace(ch)) { + // create the AreaInfo object + MinOptMax ipd = new MinOptMax(font.getCharWidth(ch)); + ai = new AreaInfo(iNextStart, (short) (iNextStart + 1), + (short) 0, (short) 0, + ipd, false, true); + vecAreaInfo.add(ai); + + // create the elements + sequence.addAll + (createElementsForASpace(alignment, ai, vecAreaInfo.size() - 1)); + + // advance to the next character + iNextStart++; + } else if (ch == NEWLINE) { // linefeed; this can happen when linefeed-treatment="preserve" // add a penalty item to the list and start a new sequence if (lineEndBAP != 0) { @@ -497,24 +576,45 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // the beginning of a word iThisStart = iNextStart; iTempStart = iNextStart; - MinOptMax wordIPD = new MinOptMax(0); for (; iTempStart < textArray.length - && textArray[iTempStart] != SPACE - && textArray[iTempStart] != NBSPACE + && !isSpace(textArray[iTempStart]) && textArray[iTempStart] != NEWLINE && !(iTempStart > iNextStart - && BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0); + && isBreakChar(textArray[iTempStart - 1])); iTempStart++) { - wordIPD.add(font.getCharWidth(textArray[iTempStart])); + //nop, just find the word boundary } - int iLetterSpaces = iTempStart - iThisStart - 1; + + //Word boundary found, process widths and kerning + int wordLength = iTempStart - iThisStart; + boolean kerning = font.hasKerning(); + MinOptMax wordIPD = new MinOptMax(0); + for (int i = iThisStart; i < iTempStart; i++) { + char c = textArray[i]; + + //character width + int charWidth = font.getCharWidth(c); + wordIPD.add(charWidth); + + //kerning + int kern = 0; + if (kerning && (i > iThisStart)) { + char previous = textArray[i - 1]; + kern = font.getKernValue(previous, c) * font.getFontSize() / 1000; + if (kern != 0) { + addToLetterAdjust(i + 1, kern); + } + wordIPD.add(kern); + } + } + + int iLetterSpaces = wordLength - 1; // if the last character is '-' or '/' and the next one // is not a space, it could be used as a line end; // add one more letter space, in case other text follows - if (BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0 - && iTempStart < textArray.length - && textArray[iTempStart] != SPACE - && textArray[iTempStart] != NBSPACE) { + if (isBreakChar(textArray[iTempStart - 1]) + && iTempStart < textArray.length + && !isSpace(textArray[iTempStart])) { iLetterSpaces++; } wordIPD.add(MinOptMax.multiply(letterSpaceIPD, iLetterSpaces)); @@ -522,12 +622,11 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // create the AreaInfo object ai = new AreaInfo(iThisStart, iTempStart, (short) 0, (short) iLetterSpaces, - wordIPD, false); + wordIPD, false, false); vecAreaInfo.add(ai); // create the elements - sequence.addAll - (createElementsForAWordFragment(alignment, ai, + sequence.addAll(createElementsForAWordFragment(alignment, ai, vecAreaInfo.size() - 1, letterSpaceIPD)); // advance to the next character @@ -666,7 +765,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { (short) (bIsWordEnd ? (iStopIndex - iStartIndex - 1) : (iStopIndex - iStartIndex)), - newIPD, bHyphenFollows), + newIPD, bHyphenFollows, false), ((LeafPosition) pos).getLeafPos())); bNothingChanged = false; } @@ -752,7 +851,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { LinkedList spaceElements = new LinkedList(); LeafPosition mainPosition = new LeafPosition(this, leafValue); - if (textArray[ai.iStartIndex] == NBSPACE) { + if (textArray[ai.iStartIndex] == CharUtilities.NBSPACE) { // a non-breaking space //TODO: other kinds of non-breaking spaces if (alignment == EN_JUSTIFY) { @@ -784,7 +883,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager { spaceElements .add(new KnuthPenalty( 0, - (textArray[ai.iStartIndex] == NBSPACE ? KnuthElement.INFINITE + (textArray[ai.iStartIndex] == CharUtilities.NBSPACE + ? KnuthElement.INFINITE : 0), false, new LeafPosition(this, -1), false)); spaceElements.add(new KnuthGlue(ai.ipdArea.opt @@ -899,8 +999,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // the fragment could end a line; in this case, it loses one // of its letter spaces; boolean bSuppressibleLetterSpace - = ai.iLScount == (ai.iBreakIndex - ai.iStartIndex) - && BREAK_CHARS.indexOf(textArray[ai.iBreakIndex - 1]) >= 0; + = /*ai.iLScount == (ai.iBreakIndex - ai.iStartIndex) + &&*/ isBreakChar(textArray[ai.iBreakIndex - 1]); if (letterSpaceWidth.min == letterSpaceWidth.max) { // constant letter spacing @@ -941,7 +1041,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // otherwise nothing happens wordElements.addAll(createElementsForAHyphen(alignment, hyphIPD, new MinOptMax(0))); } else if (bSuppressibleLetterSpace) { - // the word framgent ends with a character that acts as a hyphen + // the word fragment ends with a character that acts as a hyphen // if a break occurs the width does not increase, // otherwise there is one more letter space wordElements.addAll(createElementsForAHyphen(alignment, 0, letterSpaceWidth)); diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 8dbaeaa20..d6c2c734f 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,7 @@ import org.apache.fop.area.RegionViewport; import org.apache.fop.area.RegionReference; import org.apache.fop.area.Trait; import org.apache.fop.area.OffDocumentItem; +import org.apache.fop.area.inline.Character; import org.apache.fop.area.inline.Container; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; @@ -59,7 +60,6 @@ import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.TextArea; -import org.apache.fop.area.inline.Character; import org.apache.fop.area.inline.WordArea; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.apps.FOUserAgent; @@ -204,9 +204,9 @@ public abstract class AbstractRenderer StringBuffer sb = new StringBuffer(); for (int count = 0; count < children.size(); count++) { InlineArea inline = (InlineArea) children.get(count); - if (inline instanceof Character) { - sb.append(((Character) inline).getChar()); - } else if (inline instanceof TextArea) { + //if (inline instanceof Character) { + // sb.append(((Character) inline).getChar()); + /*} else*/ if (inline instanceof TextArea) { sb.append(((TextArea) inline).getText()); } else if (inline instanceof InlineParent) { sb.append(convertToString( @@ -618,8 +618,8 @@ public abstract class AbstractRenderer protected void renderInlineArea(InlineArea inlineArea) { if (inlineArea instanceof TextArea) { renderText((TextArea) inlineArea); - } else if (inlineArea instanceof Character) { - renderCharacter((Character) inlineArea); + //} else if (inlineArea instanceof Character) { + //renderCharacter((Character) inlineArea); } else if (inlineArea instanceof WordArea) { renderWord((WordArea) inlineArea); } else if (inlineArea instanceof SpaceArea) { @@ -640,6 +640,7 @@ public abstract class AbstractRenderer /** * Render the given Character. * @param ch the character to render + * @deprecated Only TextArea should be used. This method will be removed eventually. */ protected void renderCharacter(Character ch) { currentIPPosition += ch.getAllocIPD(); diff --git a/src/java/org/apache/fop/render/DefaultFontResolver.java b/src/java/org/apache/fop/render/DefaultFontResolver.java new file mode 100644 index 000000000..eb8a9010e --- /dev/null +++ b/src/java/org/apache/fop/render/DefaultFontResolver.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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 javax.xml.transform.Source; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontResolver; + +/** + * Default FontResolver implementation which uses the FOUserAgent to resolve font URIs. + */ +public class DefaultFontResolver implements FontResolver { + + private FOUserAgent userAgent; + + /** + * Main constructor. + * @param userAgent the user agent + */ + public DefaultFontResolver(FOUserAgent userAgent) { + this.userAgent = userAgent; + } + + /** @see org.apache.fop.fonts.FontResolver#resolve(java.lang.String) */ + public Source resolve(String href) { + return userAgent.resolveURI(href, userAgent.getFontBaseURL()); + } + +} diff --git a/src/java/org/apache/fop/render/PrintRenderer.java b/src/java/org/apache/fop/render/PrintRenderer.java index 585017a11..4de65e561 100644 --- a/src/java/org/apache/fop/render/PrintRenderer.java +++ b/src/java/org/apache/fop/render/PrintRenderer.java @@ -23,6 +23,7 @@ import org.apache.fop.area.Area; import org.apache.fop.area.Trait; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontResolver; import org.apache.fop.fonts.FontSetup; import org.apache.fop.fonts.FontTriplet; @@ -46,7 +47,8 @@ public abstract class PrintRenderer extends AbstractRenderer { */ public void setupFontInfo(FontInfo inFontInfo) { this.fontInfo = inFontInfo; - FontSetup.setup(fontInfo, fontList, userAgent); + FontResolver resolver = new DefaultFontResolver(userAgent); + FontSetup.setup(fontInfo, fontList, resolver, userAgent.isBase14KerningEnabled()); } /** diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index 5255c7727..1dfc1b429 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -55,7 +55,6 @@ import org.apache.fop.area.CTM; import org.apache.fop.area.PageViewport; import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Trait; -import org.apache.fop.area.inline.Character; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineArea; @@ -866,33 +865,6 @@ public abstract class Java2DRenderer extends AbstractRenderer implements Printab } /** - * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character) - */ - public void renderCharacter(Character ch) { - renderInlineAreaBackAndBorders(ch); - - float x = currentIPPosition + ch.getBorderAndPaddingWidthStart(); - float y = currentBPPosition + ch.getOffset() + ch.getBaselineOffset(); // baseline - - Font font = getFontFromArea(ch); - state.updateFont(font.getFontName(), font.getFontSize(), null); - - ColorType ct = (ColorType) ch.getTrait(Trait.COLOR); - state.updateColor(ct, false, null); - - String s = ch.getChar(); - state.getGraph().drawString(s, x / 1000f, y / 1000f); - - // getLogger().debug( "renderCharacter(): \"" + s + "\", x: " - // + x + ", y: " + y + state); - - // rendering text decorations - renderTextDecoration(font, ch, y, x); - - super.renderCharacter(ch); - } - - /** * Paints the text decoration marks. * * @param fs Current font diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 33602792d..1a2994145 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -47,7 +47,7 @@ import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Trait; import org.apache.fop.area.OffDocumentItem; import org.apache.fop.area.BookmarkData; -import org.apache.fop.area.inline.Character; +import org.apache.fop.area.inline.AbstractTextArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; @@ -83,6 +83,7 @@ import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.util.CharUtilities; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.xmp.XMPMetadata; @@ -185,7 +186,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** * true if a TJ command is left to be written */ - protected boolean textOpen = false; + //protected boolean textOpen = false; /** * true if a BT command has been written. @@ -196,18 +197,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * the previous Y coordinate of the last word written. * Used to decide if we can draw the next word on the same line. */ - protected int prevWordY = 0; + //protected int prevWordY = 0; /** * the previous X coordinate of the last word written. * used to calculate how much space between two words */ - protected int prevWordX = 0; + //protected int prevWordX = 0; /** * The width of the previous word. Used to calculate space between */ - protected int prevWordWidth = 0; + //protected int prevWordWidth = 0; /** * reusable word area string buffer to reduce memory usage @@ -402,6 +403,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { protected void beginTextObject() { if (!inTextMode) { currentStream.add("BT\n"); + currentFontName = ""; inTextMode = true; } } @@ -931,79 +933,6 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } /** - * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character) - */ - public void renderCharacter(Character ch) { - renderInlineAreaBackAndBorders(ch); - - beginTextObject(); - StringBuffer pdf = new StringBuffer(); - - Font font = getFontFromArea(ch); - - // This assumes that *all* CIDFonts use a /ToUnicode mapping - Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName()); - boolean useMultiByte = tf.isMultiByte(); - - // String startText = useMultiByte ? "<FEFF" : "("; - String startText = useMultiByte ? "<" : "("; - String endText = useMultiByte ? "> " : ") "; - - updateFont(font.getFontName(), font.getFontSize(), pdf); - ColorType ct = (ColorType) ch.getTrait(Trait.COLOR); - if (ct != null) { - updateColor(ct, true, pdf); - } - - // word.getOffset() = only height of text itself - // currentBlockIPPosition: 0 for beginning of line; nonzero - // where previous line area failed to take up entire allocated space - int rx = currentIPPosition + ch.getBorderAndPaddingWidthStart(); - int bl = currentBPPosition + ch.getOffset() + ch.getBaselineOffset(); - -/* log.debug("Text = " + ch.getTextArea() + - "; text width: " + ch.getWidth() + - "; BlockIP Position: " + currentBlockIPPosition + - "; currentBPPosition: " + currentBPPosition + - "; offset: " + ch.getOffset()); -*/ - // Set letterSpacing - //float ls = fs.getLetterSpacing() / this.currentFontSize; - //pdf.append(ls).append(" Tc\n"); - - if (!textOpen || bl != prevWordY) { - closeText(); - - pdf.append("1 0 0 -1 " + format(rx / 1000f) + " " + format(bl / 1000f) + " Tm " - + format(ch.getTextLetterSpaceAdjust() / 1000f) + " Tc " - + format(ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText); - prevWordY = bl; - textOpen = true; - } else { - closeText(); - - pdf.append("1 0 0 -1 " + format(rx / 1000f) + " " + format(bl / 1000f) + " Tm " - + format(ch.getTextLetterSpaceAdjust() / 1000f) + " Tc " - + format(ch.getTextWordSpaceAdjust() / 1000f) + " Tw [" + startText); - textOpen = true; - } - prevWordWidth = ch.getIPD(); - prevWordX = rx; - - String s = ch.getChar(); - - - escapeText(s, font, useMultiByte, pdf); - pdf.append(endText); - - currentStream.add(pdf.toString()); - - renderTextDecoration(tf, font.getFontSize(), ch, bl, rx); - - super.renderCharacter(ch); - } - - /** * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea) */ public void renderText(TextArea text) { @@ -1028,39 +957,17 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { int rx = currentIPPosition + text.getBorderAndPaddingWidthStart(); int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset(); -/* log.debug("Text = " + text.getTextArea() + - "; text width: " + text.getWidth() + - "; BlockIP Position: " + currentBlockIPPosition + - "; currentBPPosition: " + currentBPPosition + - "; offset: " + text.getOffset()); -*/ - // Set letterSpacing - //float ls = fs.getLetterSpacing() / this.currentFontSize; - //pdf.append(ls).append(" Tc\n"); - - if (!textOpen || bl != prevWordY) { - closeText(); - - pdf.append("1 0 0 -1 " + format(rx / 1000f) + " " + format(bl / 1000f) + " Tm " - + format(text.getTextLetterSpaceAdjust() / 1000f) + " Tc " - + format(text.getTextWordSpaceAdjust() / 1000f) + " Tw ["); - prevWordY = bl; - textOpen = true; - } else { - closeText(); - - pdf.append("1 0 0 -1 " + format(rx / 1000f) + " " + format(bl / 1000f) + " Tm " - + format(text.getTextLetterSpaceAdjust() / 1000f) + " Tc " - + format(text.getTextWordSpaceAdjust() / 1000f) + " Tw ["); - textOpen = true; - } - prevWordWidth = text.getIPD(); - prevWordX = rx; + pdf.append("1 0 0 -1 " + format(rx / 1000f) + " " + format(bl / 1000f) + " Tm " + /*+ format(text.getTextLetterSpaceAdjust() / 1000f) + " Tc\n"*/ + /*+ format(text.getTextWordSpaceAdjust() / 1000f) + " Tw ["*/); + pdf.append("["); currentStream.add(pdf.toString()); super.renderText(text); + currentStream.add("] TJ\n"); + renderTextDecoration(tf, size, text, bl, rx); } @@ -1072,17 +979,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName()); boolean useMultiByte = tf.isMultiByte(); - String startText = useMultiByte ? "<" : "("; - String endText = useMultiByte ? "> " : ") "; - StringBuffer pdf = new StringBuffer(); - pdf.append(startText); - String s = word.getWord(); - - escapeText(s, font, useMultiByte, pdf); - pdf.append(endText); + escapeText(s, word.getLetterAdjustArray(), + font, (AbstractTextArea)word.getParentArea(), useMultiByte, pdf); currentStream.add(pdf.toString()); @@ -1097,22 +998,21 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName()); boolean useMultiByte = tf.isMultiByte(); - String startText = useMultiByte ? "<" : "("; - String endText = useMultiByte ? "> " : ") "; + String s = space.getSpace(); StringBuffer pdf = new StringBuffer(); - - pdf.append(startText); - String s = space.getSpace(); - - escapeText(s, font, useMultiByte, pdf); - pdf.append(endText); - - if (useMultiByte) { - float tws = -(((TextArea) space.getParentArea()).getTextWordSpaceAdjust() - / (font.getFontSize() / 1000f)); - pdf.append(format(tws) + " "); + AbstractTextArea textArea = (AbstractTextArea)space.getParentArea(); + escapeText(s, null, font, textArea, useMultiByte, pdf); + + if (space.isAdjustable()) { + int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust() + - 2 * textArea.getTextLetterSpaceAdjust(); + + if (tws != 0) { + pdf.append(format(tws / (font.getFontSize() / 1000f))); + pdf.append(" "); + } } currentStream.add(pdf.toString()); @@ -1123,15 +1023,19 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** * Escapes text according to PDF rules. * @param s Text to escape + * @param letterAdjust an array of widths for letter adjustment (may be null) * @param fs Font state + * @param parentArea the parent text area to retrieve certain traits from * @param useMultiByte Indicates the use of multi byte convention * @param pdf target buffer for the escaped text */ - public void escapeText(String s, Font fs, + public void escapeText(String s, int[] letterAdjust, + Font fs, AbstractTextArea parentArea, boolean useMultiByte, StringBuffer pdf) { String startText = useMultiByte ? "<" : "("; String endText = useMultiByte ? "> " : ") "; + /* boolean kerningAvailable = false; Map kerning = fs.getKerning(); if (kerning != null && !kerning.isEmpty()) { @@ -1139,12 +1043,37 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { //TODO Reenable me when the layout engine supports kerning, too log.warn("Kerning support is disabled until it is supported by the layout engine!"); } + */ int l = s.length(); + float fontSize = fs.getFontSize() / 1000f; + boolean startPending = true; for (int i = 0; i < l; i++) { - char ch = fs.mapChar(s.charAt(i)); - + char orgChar = s.charAt(i); + char ch; + float glyphAdjust = 0; + if (fs.hasChar(orgChar)) { + ch = fs.mapChar(orgChar); + int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0); + glyphAdjust -= tls; + } else { + if (CharUtilities.isFixedWidthSpace(orgChar)) { + //Fixed width space are rendered as spaces so copy/paste works in a reader + ch = fs.mapChar(CharUtilities.SPACE); + glyphAdjust = fs.getCharWidth(ch) - fs.getCharWidth(orgChar); + } else { + ch = fs.mapChar(orgChar); + } + } + if (letterAdjust != null && i < l - 1) { + glyphAdjust -= letterAdjust[i + 1]; + } + + if (startPending) { + pdf.append(startText); + startPending = false; + } if (!useMultiByte) { if (ch > 127) { pdf.append("\\"); @@ -1156,6 +1085,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case '\\': pdf.append("\\"); break; + default: } pdf.append(ch); } @@ -1163,24 +1093,16 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pdf.append(PDFText.toUnicodeHex(ch)); } - if (kerningAvailable && (i + 1) < l) { - addKerning(pdf, (new Integer((int) ch)), - (new Integer((int) fs.mapChar(s.charAt(i + 1))) - ), kerning, startText, endText); + float adjust = glyphAdjust / fontSize; + + if (adjust != 0) { + pdf.append(endText).append(format(adjust)).append(' '); + startPending = true; } + } - } - - private void addKerning(StringBuffer buf, Integer ch1, Integer ch2, - Map kerning, String startText, String endText) { - Map kernPair = (Map) kerning.get(ch1); - - if (kernPair != null) { - Integer width = (Integer) kernPair.get(ch2); - if (width != null) { - buf.append(endText).append(-width.intValue()); - buf.append(' ').append(startText); - } + if (!startPending) { + pdf.append(endText); } } @@ -1189,13 +1111,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * still and writes out the TJ command to the stream if we do */ protected void closeText() { + /* if (textOpen) { currentStream.add("] TJ\n"); textOpen = false; prevWordX = 0; prevWordY = 0; currentFontName = ""; - } + }*/ } /** @@ -1319,6 +1242,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { FopPDFImage pdfimage = new FopPDFImage(fopimage, url); int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber(); fact.releaseImage(url, userAgent); + + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + placeImage((float) pos.getX() / 1000, + (float) pos.getY() / 1000, w, h, xobj); } else if ("image/jpeg".equals(mime) || "image/tiff".equals(mime)) { FopPDFImage pdfimage = new FopPDFImage(fopimage, url); int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber(); diff --git a/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java b/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java index 90cf60ce1..b2a1c07a0 100644 --- a/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java +++ b/src/java/org/apache/fop/render/ps/PSDocumentGraphics2D.java @@ -53,11 +53,6 @@ public class PSDocumentGraphics2D extends AbstractPSDocumentGraphics2D { */ PSDocumentGraphics2D(boolean textAsShapes) { super(textAsShapes); - - if (!textAsShapes) { - fontInfo = new FontInfo(); - FontSetup.setup(fontInfo, null, null); - } } /** diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index b786a4962..52e7c7bcb 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,18 +45,19 @@ import org.apache.fop.area.PageViewport; import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Trait; import org.apache.fop.area.inline.AbstractTextArea; -import org.apache.fop.area.inline.Character; import org.apache.fop.area.inline.ForeignObject; import org.apache.fop.area.inline.Image; import org.apache.fop.area.inline.InlineParent; import org.apache.fop.area.inline.Leader; +import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.ColorType; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontSetup; -import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.Typeface; import org.apache.fop.image.EPSImage; import org.apache.fop.image.FopImage; @@ -233,6 +234,16 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " M"); } + /** + * Moves the current point by (x, y) relative to the current position, + * omitting any connecting line segment. + * @param x x coordinate + * @param y y coordinate + */ + protected void rmoveTo(float x, float y) { + writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " RM"); + } + /** @see org.apache.fop.render.AbstractPathOrientedRenderer#lineTo(float, float) */ protected void lineTo(float x, float y) { writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " lineto"); @@ -310,7 +321,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } } - public void paintImage(RenderedImage image, RendererContext context, int x, int y, int width, int height) throws IOException { + /** @see org.apache.fop.render.ImageAdapter */ + public void paintImage(RenderedImage image, RendererContext context, + int x, int y, int width, int height) throws IOException { float fx = (float)x / 1000f; x += currentIPPosition / 1000f; float fy = (float)y / 1000f; @@ -822,24 +835,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } /** - * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character) - */ - public void renderCharacter(Character ch) { - String text = ch.getChar(); - renderText(ch, text); - super.renderCharacter(ch); //Updates IPD - } - - /** * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea) */ public void renderText(TextArea area) { - String text = area.getText(); - renderText(area, text); - super.renderText(area); //Updates IPD - } - - private void renderText(AbstractTextArea area, String text) { renderInlineAreaBackAndBorders(area); String fontname = getInternalFontNameForArea(area); int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE); @@ -861,24 +859,52 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } } - boolean kerningAvailable = false; - Map kerning = tf.getKerningInfo(); - if (kerning != null && !kerning.isEmpty()) { - //kerningAvailable = true; - //TODO Fix me when kerning is supported by the layout engine - log.warn("Kerning info is available, but kerning is not yet implemented for" - + " the PS renderer and not currently supported by the layout engine."); - } - beginTextObject(); writeln("1 0 0 -1 " + gen.formatDouble(rx / 1000f) + " " + gen.formatDouble(bl / 1000f) + " Tm"); + + super.renderText(area); //Updates IPD + + renderTextDecoration(tf, fontsize, area, bl, rx); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderWord(org.apache.fop.area.inline.WordArea) + */ + protected void renderWord(WordArea word) { + renderText((TextArea)word.getParentArea(), word.getWord(), word.getLetterAdjustArray()); + super.renderWord(word); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderSpace(org.apache.fop.area.inline.SpaceArea) + */ + protected void renderSpace(SpaceArea space) { + AbstractTextArea textArea = (AbstractTextArea)space.getParentArea(); + String s = space.getSpace(); + char sp = s.charAt(0); + Font font = getFontFromArea(textArea); + + int tws = (space.isAdjustable() + ? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust() + + 2 * textArea.getTextLetterSpaceAdjust() + : 0); + + rmoveTo((font.getCharWidth(sp) + tws) / 1000f, 0); + super.renderSpace(space); + } + + private void renderText(AbstractTextArea area, String text, int[] letterAdjust) { + Font font = getFontFromArea(area); + Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName()); int initialSize = text.length(); initialSize += initialSize / 2; StringBuffer sb = new StringBuffer(initialSize); int textLen = text.length(); - if (area.getTextLetterSpaceAdjust() == 0 && area.getTextWordSpaceAdjust() == 0) { + if (letterAdjust == null + && area.getTextLetterSpaceAdjust() == 0 + && area.getTextWordSpaceAdjust() == 0) { sb.append("("); for (int i = 0; i < textLen; i++) { final char c = text.charAt(i); @@ -893,17 +919,16 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda final char c = text.charAt(i); final char mapped = tf.mapChar(c); int wordSpace; - //TODO Synchronize word space behaviour with TextLayoutManager - //Check the other renderers, too! - if (CharUtilities.isAnySpace(mapped) - && mapped != CharUtilities.ZERO_WIDTH_SPACE - && mapped != CharUtilities.ZERO_WIDTH_NOBREAK_SPACE) { + + if (CharUtilities.isAdjustableSpace(mapped)) { wordSpace = area.getTextWordSpaceAdjust(); } else { wordSpace = 0; } - int cw = tf.getWidth(mapped, fontsize) / 1000; - offsets[i] = cw + area.getTextLetterSpaceAdjust() + wordSpace; + int cw = tf.getWidth(mapped, font.getFontSize()) / 1000; + int ladj = (letterAdjust != null && i < textLen - 1 ? letterAdjust[i + 1] : 0); + int tls = (i < textLen - 1 ? area.getTextLetterSpaceAdjust() : 0); + offsets[i] = cw + ladj + tls + wordSpace; PSGenerator.escapeChar(mapped, sb); } sb.append(")" + PSGenerator.LF + "["); @@ -921,7 +946,6 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } writeln(sb.toString()); - renderTextDecoration(tf, fontsize, area, bl, rx); } /** @see org.apache.fop.render.AbstractPathOrientedRenderer#breakOutOfStateStack() */ diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java index 6e0c58be2..06764c1da 100644 --- a/src/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java @@ -66,6 +66,7 @@ import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FOText; +import org.apache.fop.render.DefaultFontResolver; import org.apache.fop.render.rtf.rtflib.rtfdoc.ITableAttributes; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfAfterContainer; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfBeforeContainer; @@ -137,7 +138,7 @@ public class RTFHandler extends FOEventHandler { this.os = os; bDefer = true; - FontSetup.setup(fontInfo, null, userAgent); + FontSetup.setup(fontInfo, null, new DefaultFontResolver(userAgent)); } /** diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 9ef12c4c4..49df7b1b3 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -108,6 +108,7 @@ public class XMLRenderer extends PrintRenderer { private boolean startedSequence = false; private RendererContext context; + private boolean compactFormat = false; /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */ protected Renderer mimic; @@ -155,6 +156,10 @@ public class XMLRenderer extends PrintRenderer { XMLHandler xmlHandler = new XMLXMLHandler(); userAgent.getXMLHandlerRegistry().addXMLHandler(xmlHandler); + Boolean b = (Boolean)userAgent.getRendererOptions().get("compact-format"); + if (b != null) { + setCompactFormat(b.booleanValue()); + } } /** @@ -184,9 +189,12 @@ public class XMLRenderer extends PrintRenderer { this.handler = handler; } - private boolean isCoarseXml() { - return ((Boolean) - userAgent.getRendererOptions().get("fineDetail")).booleanValue(); + public void setCompactFormat(boolean compact) { + this.compactFormat = compact; + } + + private boolean isDetailedFormat() { + return !this.compactFormat; } /** @@ -291,16 +299,18 @@ public class XMLRenderer extends PrintRenderer { protected void addAreaAttributes(Area area) { addAttribute("ipd", area.getIPD()); addAttribute("bpd", area.getBPD()); - if (area.getIPD() != 0) { - addAttribute("ipda", area.getAllocIPD()); - } - if (area.getBPD() != 0) { - addAttribute("bpda", area.getAllocBPD()); + if (isDetailedFormat()) { + if (area.getIPD() != 0) { + addAttribute("ipda", area.getAllocIPD()); + } + if (area.getBPD() != 0) { + addAttribute("bpda", area.getAllocBPD()); + } + addAttribute("bap", area.getBorderAndPaddingWidthStart() + " " + + area.getBorderAndPaddingWidthEnd() + " " + + area.getBorderAndPaddingWidthBefore() + " " + + area.getBorderAndPaddingWidthAfter()); } - addAttribute("bap", area.getBorderAndPaddingWidthStart() + " " - + area.getBorderAndPaddingWidthEnd() + " " - + area.getBorderAndPaddingWidthBefore() + " " - + area.getBorderAndPaddingWidthAfter()); } /** @@ -758,20 +768,6 @@ public class XMLRenderer extends PrintRenderer { } /** - * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character) - */ - protected void renderCharacter(org.apache.fop.area.inline.Character ch) { - atts.clear(); - addAreaAttributes(ch); - addTraitAttributes(ch); - addAttribute("offset", ch.getOffset()); - addAttribute("baseline", ch.getBaselineOffset()); - startElement("char", atts); - characters(ch.getChar()); - endElement("char"); - } - - /** * @see org.apache.fop.render.AbstractRenderer#renderInlineSpace(Space) */ protected void renderInlineSpace(Space space) { @@ -809,6 +805,21 @@ public class XMLRenderer extends PrintRenderer { protected void renderWord(WordArea word) { atts.clear(); addAttribute("offset", word.getOffset()); + int[] letterAdjust = word.getLetterAdjustArray(); + if (letterAdjust != null) { + StringBuffer sb = new StringBuffer(64); + boolean nonZeroFound = false; + for (int i = 0, c = letterAdjust.length; i < c; i++) { + if (i > 0) { + sb.append(' '); + } + sb.append(letterAdjust[i]); + nonZeroFound |= (letterAdjust[i] != 0); + } + if (nonZeroFound) { + addAttribute("letter-adjust", sb.toString()); + } + } startElement("word", atts); characters(word.getWord()); endElement("word"); @@ -821,6 +832,9 @@ public class XMLRenderer extends PrintRenderer { protected void renderSpace(SpaceArea space) { atts.clear(); addAttribute("offset", space.getOffset()); + if (!space.isAdjustable()) { + addAttribute("adj", "false"); //default is true + } startElement("space", atts); characters(space.getSpace()); endElement("space"); diff --git a/src/java/org/apache/fop/traits/MinOptMax.java b/src/java/org/apache/fop/traits/MinOptMax.java index 143e93ae0..981abfbf4 100644 --- a/src/java/org/apache/fop/traits/MinOptMax.java +++ b/src/java/org/apache/fop/traits/MinOptMax.java @@ -168,6 +168,11 @@ public class MinOptMax implements java.io.Serializable, Cloneable { return (min != 0 || max != 0); } + /** @return true if this instance allows for shrinking or stretching */ + public boolean isElastic() { + return (min != opt || opt != max); + } + /** @see java.lang.Object#toString() */ public String toString() { StringBuffer sb = new StringBuffer(); diff --git a/src/java/org/apache/fop/traits/SpaceVal.java b/src/java/org/apache/fop/traits/SpaceVal.java index 4c9cdf5bc..d3743c88d 100644 --- a/src/java/org/apache/fop/traits/SpaceVal.java +++ b/src/java/org/apache/fop/traits/SpaceVal.java @@ -79,6 +79,7 @@ public class SpaceVal { // and stretch by a half; int spaceCharIPD = fs.getCharWidth(' '); MinOptMax space = new MinOptMax(-spaceCharIPD / 3, 0, spaceCharIPD / 2); + //TODO Adding 2 letter spaces here is not 100% correct. Spaces don't have letter spacing return new SpaceVal( MinOptMax.add (space, MinOptMax.multiply(letterSpacing.getSpace(), 2)), diff --git a/src/java/org/apache/fop/util/CharUtilities.java b/src/java/org/apache/fop/util/CharUtilities.java index 43c2c3e41..f200c3e2f 100644 --- a/src/java/org/apache/fop/util/CharUtilities.java +++ b/src/java/org/apache/fop/util/CharUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,9 +53,13 @@ public class CharUtilities { public static final int XMLWHITESPACE = 4; + /** normal space */ + public static final char SPACE = '\u0020'; + /** non-breaking space */ + public static final char NBSPACE = '\u00A0'; /** zero-width space */ public static final char ZERO_WIDTH_SPACE = '\u200B'; - /** zero-width no-break space */ + /** zero-width no-break space (= byte order mark) */ public static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF'; @@ -89,22 +93,31 @@ public class CharUtilities { * @return True if the character is a normal space */ public static boolean isBreakableSpace(char c) { - return (c == ' ' - || (c >= '\u2000' && c <= '\u200B')); -// c == '\u2000' // en quad -// c == '\u2001' // em quad -// c == '\u2002' // en space -// c == '\u2003' // em space -// c == '\u2004' // three-per-em space -// c == '\u2005' // four--per-em space -// c == '\u2006' // six-per-em space -// c == '\u2007' // figure space -// c == '\u2008' // punctuation space -// c == '\u2009' // thin space -// c == '\u200A' // hair space -// c == '\u200B' // zero width space + return (c == SPACE || isFixedWidthSpace(c)); } - + + /** + * Method to determine if the character is a (breakable) fixed-width space. + * @param c the character to check + * @return true if the character has a fixed-width + */ + public static boolean isFixedWidthSpace(char c) { + return (c >= '\u2000' && c <= '\u200B') || c == '\u3000'; +// c == '\u2000' // en quad +// c == '\u2001' // em quad +// c == '\u2002' // en space +// c == '\u2003' // em space +// c == '\u2004' // three-per-em space +// c == '\u2005' // four--per-em space +// c == '\u2006' // six-per-em space +// c == '\u2007' // figure space +// c == '\u2008' // punctuation space +// c == '\u2009' // thin space +// c == '\u200A' // hair space +// c == '\u200B' // zero width space +// c == '\u3000' // ideographic space + } + /** * Method to determine if the character is a nonbreaking * space. @@ -113,7 +126,7 @@ public class CharUtilities { */ public static boolean isNonBreakableSpace(char c) { return - (c == '\u00A0' // no-break space + (c == NBSPACE // no-break space || c == '\u202F' // narrow no-break space || c == '\u3000' // ideographic space || c == ZERO_WIDTH_NOBREAK_SPACE); // zero width no-break space @@ -141,5 +154,30 @@ public class CharUtilities { boolean ret = (isBreakableSpace(c) || isNonBreakableSpace(c)); return ret; } + + /** + * Indicates whether a character is classified as "Alphabetic" by the Unicode standard. + * @param ch the character + * @return true if the character is "Alphabetic" + */ + public static boolean isAlphabetic(char ch) { + //http://www.unicode.org/Public/UNIDATA/UCD.html#Alphabetic + //Generated from: Other_Alphabetic + Lu + Ll + Lt + Lm + Lo + Nl + int generalCategory = Character.getType(ch); + switch (generalCategory) { + case Character.UPPERCASE_LETTER: //Lu + case Character.LOWERCASE_LETTER: //Ll + case Character.TITLECASE_LETTER: //Lt + case Character.MODIFIER_LETTER: //Lm + case Character.OTHER_LETTER: //Lo + case Character.LETTER_NUMBER: //Nl + return true; + default: + //TODO if (ch in Other_Alphabetic) return true; (Probably need ICU4J for that) + //Other_Alphabetic contains mostly more exotic characters + return false; + } + } + } |