Lines 19-573
Link Here
|
19 |
|
19 |
|
20 |
package org.apache.fop.render.ps; |
20 |
package org.apache.fop.render.ps; |
21 |
|
21 |
|
|
|
22 |
import java.awt.BasicStroke; |
22 |
import java.awt.Color; |
23 |
import java.awt.Color; |
23 |
import java.awt.Graphics2D; |
24 |
import java.awt.Graphics2D; |
24 |
import java.awt.Paint; |
25 |
import java.awt.Paint; |
25 |
import java.awt.Shape; |
26 |
import java.awt.Shape; |
26 |
import java.awt.Stroke; |
27 |
import java.awt.Stroke; |
27 |
import java.awt.font.TextAttribute; |
28 |
import java.awt.geom.AffineTransform; |
|
|
29 |
import java.awt.geom.Ellipse2D; |
30 |
import java.awt.geom.GeneralPath; |
31 |
import java.awt.geom.PathIterator; |
28 |
import java.awt.geom.Point2D; |
32 |
import java.awt.geom.Point2D; |
29 |
import java.awt.geom.Rectangle2D; |
|
|
30 |
import java.io.IOException; |
33 |
import java.io.IOException; |
31 |
import java.text.AttributedCharacterIterator; |
34 |
import java.text.AttributedCharacterIterator; |
32 |
import java.text.CharacterIterator; |
|
|
33 |
import java.util.Iterator; |
35 |
import java.util.Iterator; |
34 |
import java.util.List; |
36 |
import java.util.List; |
35 |
|
37 |
|
36 |
import org.apache.batik.dom.svg.SVGOMTextElement; |
38 |
import org.apache.batik.gvt.font.GVTGlyphVector; |
37 |
import org.apache.batik.gvt.TextNode; |
|
|
38 |
import org.apache.batik.gvt.TextPainter; |
39 |
import org.apache.batik.gvt.font.GVTFontFamily; |
40 |
import org.apache.batik.gvt.renderer.StrokingTextPainter; |
41 |
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; |
42 |
import org.apache.batik.gvt.text.Mark; |
43 |
import org.apache.batik.gvt.text.TextPaintInfo; |
39 |
import org.apache.batik.gvt.text.TextPaintInfo; |
44 |
import org.apache.commons.logging.Log; |
40 |
import org.apache.batik.gvt.text.TextSpanLayout; |
45 |
import org.apache.commons.logging.LogFactory; |
41 |
|
|
|
42 |
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; |
43 |
import org.apache.xmlgraphics.ps.PSGenerator; |
44 |
import org.apache.xmlgraphics.ps.PSResource; |
45 |
|
46 |
import org.apache.fop.fonts.Font; |
46 |
import org.apache.fop.fonts.Font; |
47 |
import org.apache.fop.fonts.FontInfo; |
47 |
import org.apache.fop.fonts.FontInfo; |
48 |
import org.apache.fop.fonts.FontTriplet; |
48 |
import org.apache.fop.svg.NativeTextPainter; |
49 |
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; |
49 |
import org.apache.fop.util.CharUtilities; |
50 |
|
50 |
|
51 |
|
|
|
52 |
/** |
51 |
/** |
53 |
* Renders the attributed character iterator of a <tt>TextNode</tt>. |
52 |
* Renders the attributed character iterator of a <tt>TextNode</tt>. |
54 |
* This class draws the text directly into the PSGraphics2D so that |
53 |
* This class draws the text directly using PostScript text operators so |
55 |
* the text is not drawn using shapes which makes the PS files larger. |
54 |
* the text is not drawn using shapes which makes the PS files larger. |
56 |
* If the text is simple enough to draw then it sets the font and calls |
55 |
* <p> |
57 |
* drawString. If the text is complex or the cannot be translated |
56 |
* The text runs are split into smaller text runs that can be bundles in single |
58 |
* into a simple drawString the StrokingTextPainter is used instead. |
57 |
* calls of the xshow, yshow or xyshow operators. For outline text, the charpath |
59 |
* |
58 |
* operator is used. |
60 |
* (todo) handle underline, overline and strikethrough |
|
|
61 |
* (todo) use drawString(AttributedCharacterIterator iterator...) for some |
62 |
* |
63 |
* @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> |
64 |
* @version $Id$ |
65 |
*/ |
59 |
*/ |
66 |
public class PSTextPainter implements TextPainter { |
60 |
public class PSTextPainter extends NativeTextPainter { |
67 |
|
61 |
|
68 |
/** the logger for this class */ |
62 |
private static final boolean DEBUG = false; |
69 |
protected Log log = LogFactory.getLog(PSTextPainter.class); |
|
|
70 |
|
63 |
|
71 |
private final NativeTextHandler nativeTextHandler; |
64 |
private FontResourceCache fontResources; |
72 |
private final FontInfo fontInfo; |
|
|
73 |
|
65 |
|
74 |
/** |
66 |
private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); |
75 |
* Use the stroking text painter to get the bounds and shape. |
|
|
76 |
* Also used as a fallback to draw the string with strokes. |
77 |
*/ |
78 |
protected static final TextPainter |
79 |
PROXY_PAINTER = StrokingTextPainter.getInstance(); |
80 |
|
67 |
|
81 |
/** |
68 |
/** |
82 |
* Create a new PS text painter with the given font information. |
69 |
* Create a new PS text painter with the given font information. |
83 |
* @param nativeTextHandler the NativeTextHandler instance used for text painting |
70 |
* @param fontInfo the font collection |
84 |
*/ |
71 |
*/ |
85 |
public PSTextPainter(NativeTextHandler nativeTextHandler) { |
72 |
public PSTextPainter(FontInfo fontInfo) { |
86 |
this.nativeTextHandler = nativeTextHandler; |
73 |
super(fontInfo); |
87 |
this.fontInfo = nativeTextHandler.getFontInfo(); |
74 |
this.fontResources = new FontResourceCache(fontInfo); |
88 |
} |
75 |
} |
89 |
|
76 |
|
90 |
/** |
77 |
/** {@inheritDoc} */ |
91 |
* Paints the specified attributed character iterator using the |
78 |
protected boolean isSupported(Graphics2D g2d) { |
92 |
* specified Graphics2D and context and font context. |
79 |
return g2d instanceof PSGraphics2D; |
93 |
* @param node the TextNode to paint |
|
|
94 |
* @param g2d the Graphics2D to use |
95 |
*/ |
96 |
public void paint(TextNode node, Graphics2D g2d) { |
97 |
String txt = node.getText(); |
98 |
Point2D loc = node.getLocation(); |
99 |
|
100 |
if (hasUnsupportedAttributes(node)) { |
101 |
PROXY_PAINTER.paint(node, g2d); |
102 |
} else { |
103 |
paintTextRuns(node.getTextRuns(), g2d, loc); |
104 |
} |
105 |
} |
80 |
} |
106 |
|
81 |
|
|
|
82 |
/** {@inheritDoc} */ |
83 |
protected void paintTextRun(TextRun textRun, Graphics2D g2d) throws IOException { |
84 |
AttributedCharacterIterator runaci = textRun.getACI(); |
85 |
runaci.first(); |
107 |
|
86 |
|
108 |
private boolean hasUnsupportedAttributes(TextNode node) { |
87 |
TextPaintInfo tpi = (TextPaintInfo)runaci.getAttribute(PAINT_INFO); |
109 |
Iterator i = node.getTextRuns().iterator(); |
88 |
if (tpi == null || !tpi.visible) { |
110 |
while (i.hasNext()) { |
89 |
return; |
111 |
StrokingTextPainter.TextRun |
|
|
112 |
run = (StrokingTextPainter.TextRun)i.next(); |
113 |
AttributedCharacterIterator aci = run.getACI(); |
114 |
boolean hasUnsupported = hasUnsupportedAttributes(aci); |
115 |
if (hasUnsupported) { |
116 |
return true; |
117 |
} |
118 |
} |
90 |
} |
119 |
return false; |
91 |
if ((tpi != null) && (tpi.composite != null)) { |
120 |
} |
92 |
g2d.setComposite(tpi.composite); |
|
|
93 |
} |
121 |
|
94 |
|
122 |
private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { |
95 |
//------------------------------------ |
123 |
boolean hasunsupported = false; |
96 |
TextSpanLayout layout = textRun.getLayout(); |
|
|
97 |
logTextRun(runaci, layout); |
98 |
CharSequence chars = collectCharacters(runaci); |
99 |
runaci.first(); //Reset ACI |
124 |
|
100 |
|
125 |
String text = getText(aci); |
101 |
final PSGraphics2D ps = (PSGraphics2D)g2d; |
126 |
Font font = makeFont(aci); |
102 |
final PSGenerator gen = ps.getPSGenerator(); |
127 |
if (hasUnsupportedGlyphs(text, font)) { |
103 |
ps.preparePainting(); |
128 |
log.trace("-> Unsupported glyphs found"); |
|
|
129 |
hasunsupported = true; |
130 |
} |
131 |
|
104 |
|
132 |
TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( |
105 |
if (DEBUG) { |
133 |
GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); |
106 |
log.debug("Text: " + chars); |
134 |
if ((tpi != null) |
107 |
gen.commentln("%Text: " + chars); |
135 |
&& ((tpi.strokeStroke != null && tpi.strokePaint != null) |
|
|
136 |
|| (tpi.strikethroughStroke != null) |
137 |
|| (tpi.underlineStroke != null) |
138 |
|| (tpi.overlineStroke != null))) { |
139 |
log.trace("-> under/overlines etc. found"); |
140 |
hasunsupported = true; |
141 |
} |
108 |
} |
142 |
|
109 |
|
143 |
//Alpha is not supported |
110 |
GeneralPath debugShapes = null; |
144 |
Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND); |
111 |
if (DEBUG) { |
145 |
if (foreground instanceof Color) { |
112 |
debugShapes = new GeneralPath(); |
146 |
Color col = (Color)foreground; |
|
|
147 |
if (col.getAlpha() != 255) { |
148 |
log.trace("-> transparency found"); |
149 |
hasunsupported = true; |
150 |
} |
151 |
} |
113 |
} |
152 |
|
114 |
|
153 |
Object letSpace = aci.getAttribute( |
115 |
TextUtil textUtil = new TextUtil(gen); |
154 |
GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); |
116 |
textUtil.setupFonts(runaci); |
155 |
if (letSpace != null) { |
117 |
if (!textUtil.hasFonts()) { |
156 |
log.trace("-> letter spacing found"); |
118 |
//Draw using Java2D when no native fonts are available |
157 |
hasunsupported = true; |
119 |
textRun.getLayout().draw(g2d); |
|
|
120 |
return; |
158 |
} |
121 |
} |
159 |
|
122 |
|
160 |
Object wordSpace = aci.getAttribute( |
123 |
gen.saveGraphicsState(); |
161 |
GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); |
124 |
gen.concatMatrix(g2d.getTransform()); |
162 |
if (wordSpace != null) { |
125 |
Shape imclip = g2d.getClip(); |
163 |
log.trace("-> word spacing found"); |
126 |
clip(ps, imclip); |
164 |
hasunsupported = true; |
|
|
165 |
} |
166 |
|
127 |
|
167 |
Object lengthAdjust = aci.getAttribute( |
128 |
gen.writeln("BT"); //beginTextObject() |
168 |
GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); |
|
|
169 |
if (lengthAdjust != null) { |
170 |
log.trace("-> length adjustments found"); |
171 |
hasunsupported = true; |
172 |
} |
173 |
|
129 |
|
174 |
Object writeMod = aci.getAttribute( |
130 |
AffineTransform localTransform = new AffineTransform(); |
175 |
GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE); |
131 |
Point2D prevPos = null; |
176 |
if (writeMod != null |
132 |
GVTGlyphVector gv = layout.getGlyphVector(); |
177 |
&& !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals( |
133 |
PSTextRun psRun = new PSTextRun(); //Used to split a text run into smaller runs |
178 |
writeMod)) { |
134 |
for (int index = 0, c = gv.getNumGlyphs(); index < c; index++) { |
179 |
log.trace("-> Unsupported writing modes found"); |
135 |
char ch = chars.charAt(index); |
180 |
hasunsupported = true; |
136 |
boolean visibleChar = gv.isGlyphVisible(index) |
181 |
} |
137 |
|| (CharUtilities.isAnySpace(ch) && !CharUtilities.isZeroWidthSpace(ch)); |
|
|
138 |
logCharacter(ch, layout, index, visibleChar); |
139 |
if (!visibleChar) { |
140 |
continue; |
141 |
} |
142 |
Point2D glyphPos = gv.getGlyphPosition(index); |
182 |
|
143 |
|
183 |
Object vertOr = aci.getAttribute( |
144 |
AffineTransform glyphTransform = gv.getGlyphTransform(index); |
184 |
GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); |
145 |
if (log.isTraceEnabled()) { |
185 |
if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( |
146 |
log.trace("pos " + glyphPos + ", transform " + glyphTransform); |
186 |
vertOr)) { |
147 |
} |
187 |
log.trace("-> vertical orientation found"); |
148 |
if (DEBUG) { |
188 |
hasunsupported = true; |
149 |
Shape sh = gv.getGlyphLogicalBounds(index); |
189 |
} |
150 |
if (sh == null) { |
|
|
151 |
sh = new Ellipse2D.Double(glyphPos.getX(), glyphPos.getY(), 2, 2); |
152 |
} |
153 |
debugShapes.append(sh, false); |
154 |
} |
190 |
|
155 |
|
191 |
Object rcDel = aci.getAttribute( |
156 |
//Exact position of the glyph |
192 |
GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER); |
157 |
localTransform.setToIdentity(); |
193 |
//Batik 1.6 returns null here which makes it impossible to determine whether this can |
158 |
localTransform.translate(glyphPos.getX(), glyphPos.getY()); |
194 |
//be painted or not, i.e. fall back to stroking. :-( |
159 |
if (glyphTransform != null) { |
195 |
if (/*rcDel != null &&*/ !(rcDel instanceof SVGOMTextElement)) { |
160 |
localTransform.concatenate(glyphTransform); |
196 |
log.trace("-> spans found"); |
161 |
} |
197 |
hasunsupported = true; //Filter spans |
162 |
localTransform.scale(1, -1); |
|
|
163 |
|
164 |
boolean flushCurrentRun = false; |
165 |
//Try to optimize by combining characters using the same font and on the same line. |
166 |
if (glyphTransform != null) { |
167 |
//Happens for text-on-a-path |
168 |
flushCurrentRun = true; |
169 |
} |
170 |
if (psRun.getRunLength() >= 128) { |
171 |
//Don't let a run get too long |
172 |
flushCurrentRun = true; |
173 |
} |
174 |
|
175 |
//Note the position of the glyph relative to the previous one |
176 |
Point2D relPos; |
177 |
if (prevPos == null) { |
178 |
relPos = new Point2D.Double(0, 0); |
179 |
} else { |
180 |
relPos = new Point2D.Double( |
181 |
glyphPos.getX() - prevPos.getX(), |
182 |
glyphPos.getY() - prevPos.getY()); |
183 |
} |
184 |
if (psRun.vertChanges == 0 |
185 |
&& psRun.getHorizRunLength() > 2 |
186 |
&& relPos.getY() != 0) { |
187 |
//new line |
188 |
flushCurrentRun = true; |
189 |
} |
190 |
|
191 |
//Select the actual character to paint |
192 |
char paintChar = (CharUtilities.isAnySpace(ch) ? ' ' : ch); |
193 |
|
194 |
//Select (sub)font for character |
195 |
Font f = textUtil.selectFontForChar(paintChar); |
196 |
char mapped = f.mapChar(ch); |
197 |
boolean fontChanging = textUtil.isFontChanging(f, mapped); |
198 |
if (fontChanging) { |
199 |
flushCurrentRun = true; |
200 |
} |
201 |
|
202 |
if (flushCurrentRun) { |
203 |
//Paint the current run and reset for the next run |
204 |
psRun.paint(ps, textUtil, tpi); |
205 |
psRun.reset(); |
206 |
} |
207 |
|
208 |
//Track current run |
209 |
psRun.addCharacter(paintChar, relPos); |
210 |
psRun.noteStartingTransformation(localTransform); |
211 |
|
212 |
//Change font if necessary |
213 |
if (fontChanging) { |
214 |
textUtil.setCurrentFont(f, mapped); |
215 |
} |
216 |
|
217 |
//Update last position |
218 |
prevPos = glyphPos; |
198 |
} |
219 |
} |
|
|
220 |
psRun.paint(ps, textUtil, tpi); |
221 |
gen.writeln("ET"); //endTextObject() |
222 |
gen.restoreGraphicsState(); |
199 |
|
223 |
|
200 |
if (hasunsupported) { |
224 |
if (DEBUG) { |
201 |
log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); |
225 |
//Paint debug shapes |
|
|
226 |
g2d.setStroke(new BasicStroke(0)); |
227 |
g2d.setColor(Color.LIGHT_GRAY); |
228 |
g2d.draw(debugShapes); |
202 |
} |
229 |
} |
203 |
return hasunsupported; |
|
|
204 |
} |
230 |
} |
205 |
|
231 |
|
206 |
/** |
232 |
private void applyColor(Paint paint, final PSGenerator gen) throws IOException { |
207 |
* Paint a list of text runs on the Graphics2D at a given location. |
233 |
if (paint == null) { |
208 |
* @param textRuns the list of text runs |
234 |
return; |
209 |
* @param g2d the Graphics2D to paint to |
235 |
} else if (paint instanceof Color) { |
210 |
* @param loc the current location of the "cursor" |
236 |
Color col = (Color)paint; |
211 |
*/ |
237 |
gen.useColor(col); |
212 |
protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) { |
238 |
} else { |
213 |
Point2D currentloc = loc; |
239 |
log.warn("Paint not supported: " + paint.toString()); |
214 |
Iterator i = textRuns.iterator(); |
|
|
215 |
while (i.hasNext()) { |
216 |
StrokingTextPainter.TextRun |
217 |
run = (StrokingTextPainter.TextRun)i.next(); |
218 |
currentloc = paintTextRun(run, g2d, currentloc); |
219 |
} |
240 |
} |
220 |
} |
241 |
} |
221 |
|
242 |
|
222 |
/** |
243 |
private PSResource getResourceForFont(Font f, String postfix) { |
223 |
* Paint a single text run on the Graphics2D at a given location. |
244 |
String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); |
224 |
* @param run the text run to paint |
245 |
return this.fontResources.getPSResourceForFontKey(key); |
225 |
* @param g2d the Graphics2D to paint to |
|
|
226 |
* @param loc the current location of the "cursor" |
227 |
* @return the new location of the "cursor" after painting the text run |
228 |
*/ |
229 |
protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) { |
230 |
AttributedCharacterIterator aci = run.getACI(); |
231 |
return paintACI(aci, g2d, loc); |
232 |
} |
246 |
} |
233 |
|
247 |
|
234 |
/** |
248 |
private void clip(PSGraphics2D ps, Shape shape) throws IOException { |
235 |
* Extract the raw text from an ACI. |
249 |
if (shape == null) { |
236 |
* @param aci ACI to inspect |
250 |
return; |
237 |
* @return the extracted text |
|
|
238 |
*/ |
239 |
protected String getText(AttributedCharacterIterator aci) { |
240 |
StringBuffer sb = new StringBuffer(aci.getEndIndex() - aci.getBeginIndex()); |
241 |
for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) { |
242 |
sb.append(c); |
243 |
} |
251 |
} |
244 |
return sb.toString(); |
252 |
ps.getPSGenerator().writeln("newpath"); |
|
|
253 |
PathIterator iter = shape.getPathIterator(IDENTITY_TRANSFORM); |
254 |
ps.processPathIterator(iter); |
255 |
ps.getPSGenerator().writeln("clip"); |
245 |
} |
256 |
} |
246 |
|
257 |
|
247 |
/** |
258 |
private class TextUtil { |
248 |
* Paint an ACI on a Graphics2D at a given location. The method has to |
|
|
249 |
* update the location after painting. |
250 |
* @param aci ACI to paint |
251 |
* @param g2d Graphics2D to paint on |
252 |
* @param loc start location |
253 |
* @return new current location |
254 |
*/ |
255 |
protected Point2D paintACI(AttributedCharacterIterator aci, Graphics2D g2d, Point2D loc) { |
256 |
//ACIUtils.dumpAttrs(aci); |
257 |
|
259 |
|
258 |
aci.first(); |
260 |
private PSGenerator gen; |
|
|
261 |
private Font[] fonts; |
262 |
private Font currentFont; |
263 |
private int currentEncoding = -1; |
259 |
|
264 |
|
260 |
updateLocationFromACI(aci, loc); |
265 |
public TextUtil(PSGenerator gen) { |
261 |
|
266 |
this.gen = gen; |
262 |
TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( |
|
|
263 |
GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); |
264 |
|
265 |
if (tpi == null) { |
266 |
return loc; |
267 |
} |
267 |
} |
268 |
|
268 |
|
269 |
TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute( |
269 |
public Font selectFontForChar(char ch) { |
270 |
GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); |
270 |
for (int i = 0, c = fonts.length; i < c; i++) { |
271 |
|
271 |
if (fonts[i].hasChar(ch)) { |
272 |
//Set up font |
272 |
return fonts[i]; |
273 |
List gvtFonts = (List)aci.getAttribute( |
273 |
} |
274 |
GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); |
274 |
} |
275 |
Paint foreground = tpi.fillPaint; |
275 |
return fonts[0]; //TODO Maybe fall back to painting with shapes |
276 |
Paint strokePaint = tpi.strokePaint; |
|
|
277 |
Stroke stroke = tpi.strokeStroke; |
278 |
|
279 |
Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); |
280 |
if (fontSize == null) { |
281 |
return loc; |
282 |
} |
276 |
} |
283 |
Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); |
|
|
284 |
Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); |
285 |
|
277 |
|
286 |
if (foreground instanceof Color) { |
278 |
public void writeTextMatrix(AffineTransform transform) throws IOException { |
287 |
Color col = (Color)foreground; |
279 |
double[] matrix = new double[6]; |
288 |
g2d.setColor(col); |
280 |
transform.getMatrix(matrix); |
|
|
281 |
gen.writeln(gen.formatDouble5(matrix[0]) + " " |
282 |
+ gen.formatDouble5(matrix[1]) + " " |
283 |
+ gen.formatDouble5(matrix[2]) + " " |
284 |
+ gen.formatDouble5(matrix[3]) + " " |
285 |
+ gen.formatDouble5(matrix[4]) + " " |
286 |
+ gen.formatDouble5(matrix[5]) + " Tm"); |
289 |
} |
287 |
} |
290 |
g2d.setPaint(foreground); |
|
|
291 |
g2d.setStroke(stroke); |
292 |
|
288 |
|
293 |
Font font = makeFont(aci); |
289 |
public boolean isFontChanging(Font f, char mapped) { |
294 |
java.awt.Font awtFont = makeAWTFont(aci, font); |
290 |
if (f != getCurrentFont()) { |
295 |
|
291 |
int encoding = mapped / 256; |
296 |
g2d.setFont(awtFont); |
292 |
if (encoding != getCurrentFontEncoding()) { |
297 |
|
293 |
return true; //Font is changing |
298 |
String txt = getText(aci); |
294 |
} |
299 |
float advance = getStringWidth(txt, font); |
|
|
300 |
float tx = 0; |
301 |
if (anchor != null) { |
302 |
switch (anchor.getType()) { |
303 |
case TextNode.Anchor.ANCHOR_MIDDLE: |
304 |
tx = -advance / 2; |
305 |
break; |
306 |
case TextNode.Anchor.ANCHOR_END: |
307 |
tx = -advance; |
308 |
break; |
309 |
default: //nop |
310 |
} |
295 |
} |
|
|
296 |
return false; //Font is the same |
311 |
} |
297 |
} |
312 |
|
298 |
|
313 |
drawPrimitiveString(g2d, loc, font, txt, tx); |
299 |
public void selectFont(Font f, char mapped) throws IOException { |
314 |
loc.setLocation(loc.getX() + advance, loc.getY()); |
300 |
int encoding = mapped / 256; |
315 |
return loc; |
301 |
String postfix = (encoding == 0 ? null : Integer.toString(encoding)); |
316 |
} |
302 |
PSResource res = getResourceForFont(f, postfix); |
|
|
303 |
gen.useFont("/" + res.getName(), f.getFontSize() / 1000f); |
304 |
gen.getResourceTracker().notifyResourceUsageOnPage(res); |
305 |
} |
317 |
|
306 |
|
318 |
protected void drawPrimitiveString(Graphics2D g2d, Point2D loc, Font font, String txt, float tx) { |
307 |
public Font getCurrentFont() { |
319 |
//Finally draw text |
308 |
return this.currentFont; |
320 |
nativeTextHandler.setOverrideFont(font); |
|
|
321 |
try { |
322 |
try { |
323 |
nativeTextHandler.drawString(txt, (float)(loc.getX() + tx), (float)(loc.getY())); |
324 |
} catch (IOException ioe) { |
325 |
if (g2d instanceof PSGraphics2D) { |
326 |
((PSGraphics2D)g2d).handleIOException(ioe); |
327 |
} |
328 |
} |
329 |
} finally { |
330 |
nativeTextHandler.setOverrideFont(null); |
331 |
} |
309 |
} |
332 |
} |
|
|
333 |
|
310 |
|
334 |
private void updateLocationFromACI( |
311 |
public int getCurrentFontEncoding() { |
335 |
AttributedCharacterIterator aci, |
312 |
return this.currentEncoding; |
336 |
Point2D loc) { |
|
|
337 |
//Adjust position of span |
338 |
Float xpos = (Float)aci.getAttribute( |
339 |
GVTAttributedCharacterIterator.TextAttribute.X); |
340 |
Float ypos = (Float)aci.getAttribute( |
341 |
GVTAttributedCharacterIterator.TextAttribute.Y); |
342 |
Float dxpos = (Float)aci.getAttribute( |
343 |
GVTAttributedCharacterIterator.TextAttribute.DX); |
344 |
Float dypos = (Float)aci.getAttribute( |
345 |
GVTAttributedCharacterIterator.TextAttribute.DY); |
346 |
if (xpos != null) { |
347 |
loc.setLocation(xpos.doubleValue(), loc.getY()); |
348 |
} |
313 |
} |
349 |
if (ypos != null) { |
314 |
|
350 |
loc.setLocation(loc.getX(), ypos.doubleValue()); |
315 |
public void setCurrentFont(Font font, int encoding) { |
|
|
316 |
this.currentFont = font; |
317 |
this.currentEncoding = encoding; |
351 |
} |
318 |
} |
352 |
if (dxpos != null) { |
319 |
|
353 |
loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); |
320 |
public void setCurrentFont(Font font, char mapped) { |
|
|
321 |
int encoding = mapped / 256; |
322 |
setCurrentFont(font, encoding); |
354 |
} |
323 |
} |
355 |
if (dypos != null) { |
324 |
|
356 |
loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); |
325 |
public void setupFonts(AttributedCharacterIterator runaci) { |
|
|
326 |
this.fonts = findFonts(runaci); |
357 |
} |
327 |
} |
358 |
} |
|
|
359 |
|
328 |
|
360 |
private String getStyle(AttributedCharacterIterator aci) { |
329 |
public boolean hasFonts() { |
361 |
Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); |
330 |
return (fonts != null) && (fonts.length > 0); |
362 |
return ((posture != null) && (posture.floatValue() > 0.0)) |
331 |
} |
363 |
? "italic" |
|
|
364 |
: "normal"; |
365 |
} |
366 |
|
332 |
|
367 |
private int getWeight(AttributedCharacterIterator aci) { |
|
|
368 |
Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); |
369 |
return ((taWeight != null) && (taWeight.floatValue() > 1.0)) |
370 |
? Font.WEIGHT_BOLD |
371 |
: Font.WEIGHT_NORMAL; |
372 |
} |
333 |
} |
373 |
|
334 |
|
374 |
private Font makeFont(AttributedCharacterIterator aci) { |
335 |
private class PSTextRun { |
375 |
Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); |
336 |
|
376 |
if (fontSize == null) { |
337 |
private AffineTransform textTransform; |
377 |
fontSize = new Float(10.0f); |
338 |
private List relativePositions = new java.util.LinkedList(); |
|
|
339 |
private StringBuffer currentChars = new StringBuffer(); |
340 |
private int horizChanges = 0; |
341 |
private int vertChanges = 0; |
342 |
|
343 |
public void reset() { |
344 |
textTransform = null; |
345 |
currentChars.setLength(0); |
346 |
horizChanges = 0; |
347 |
vertChanges = 0; |
348 |
relativePositions.clear(); |
378 |
} |
349 |
} |
379 |
String style = getStyle(aci); |
|
|
380 |
int weight = getWeight(aci); |
381 |
|
350 |
|
382 |
String fontFamily = null; |
351 |
public int getHorizRunLength() { |
383 |
List gvtFonts = (List) aci.getAttribute( |
352 |
if (this.vertChanges == 0 |
384 |
GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); |
353 |
&& getRunLength() > 0) { |
385 |
if (gvtFonts != null) { |
354 |
return getRunLength(); |
386 |
Iterator i = gvtFonts.iterator(); |
|
|
387 |
while (i.hasNext()) { |
388 |
GVTFontFamily fam = (GVTFontFamily) i.next(); |
389 |
/* (todo) Enable SVG Font painting |
390 |
if (fam instanceof SVGFontFamily) { |
391 |
PROXY_PAINTER.paint(node, g2d); |
392 |
return; |
393 |
}*/ |
394 |
fontFamily = fam.getFamilyName(); |
395 |
if (fontInfo.hasFont(fontFamily, style, weight)) { |
396 |
FontTriplet triplet = fontInfo.fontLookup( |
397 |
fontFamily, style, weight); |
398 |
int fsize = (int)(fontSize.floatValue() * 1000); |
399 |
return fontInfo.getFontInstance(triplet, fsize); |
400 |
} |
401 |
} |
355 |
} |
|
|
356 |
return 0; |
402 |
} |
357 |
} |
403 |
FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); |
|
|
404 |
int fsize = (int)(fontSize.floatValue() * 1000); |
405 |
return fontInfo.getFontInstance(triplet, fsize); |
406 |
} |
407 |
|
358 |
|
408 |
private java.awt.Font makeAWTFont(AttributedCharacterIterator aci, Font font) { |
359 |
public void addCharacter(char paintChar, Point2D relPos) { |
409 |
final String style = getStyle(aci); |
360 |
addRelativePosition(relPos); |
410 |
final int weight = getWeight(aci); |
361 |
currentChars.append(paintChar); |
411 |
int fStyle = java.awt.Font.PLAIN; |
|
|
412 |
if (weight == Font.WEIGHT_BOLD) { |
413 |
fStyle |= java.awt.Font.BOLD; |
414 |
} |
362 |
} |
415 |
if ("italic".equals(style)) { |
|
|
416 |
fStyle |= java.awt.Font.ITALIC; |
417 |
} |
418 |
return new java.awt.Font(font.getFontName(), fStyle, |
419 |
(font.getFontSize() / 1000)); |
420 |
} |
421 |
|
363 |
|
422 |
private float getStringWidth(String str, Font font) { |
364 |
private void addRelativePosition(Point2D relPos) { |
423 |
float wordWidth = 0; |
365 |
if (getRunLength() > 0) { |
424 |
float whitespaceWidth = font.getWidth(font.mapChar(' ')); |
366 |
if (relPos.getX() != 0) { |
425 |
|
367 |
horizChanges++; |
426 |
for (int i = 0; i < str.length(); i++) { |
|
|
427 |
float charWidth; |
428 |
char c = str.charAt(i); |
429 |
if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { |
430 |
charWidth = font.getWidth(font.mapChar(c)); |
431 |
if (charWidth <= 0) { |
432 |
charWidth = whitespaceWidth; |
433 |
} |
368 |
} |
434 |
} else { |
369 |
if (relPos.getY() != 0) { |
435 |
charWidth = whitespaceWidth; |
370 |
vertChanges++; |
|
|
371 |
} |
436 |
} |
372 |
} |
437 |
wordWidth += charWidth; |
373 |
relativePositions.add(relPos); |
438 |
} |
374 |
} |
439 |
return wordWidth / 1000f; |
|
|
440 |
} |
441 |
|
375 |
|
442 |
private boolean hasUnsupportedGlyphs(String str, Font font) { |
376 |
public void noteStartingTransformation(AffineTransform transform) { |
443 |
for (int i = 0; i < str.length(); i++) { |
377 |
if (textTransform == null) { |
444 |
float charWidth; |
378 |
this.textTransform = new AffineTransform(transform); |
445 |
char c = str.charAt(i); |
|
|
446 |
if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { |
447 |
if (!font.hasChar(c)) { |
448 |
return true; |
449 |
} |
450 |
} |
379 |
} |
451 |
} |
380 |
} |
452 |
return false; |
|
|
453 |
} |
454 |
|
381 |
|
455 |
/** |
382 |
public int getRunLength() { |
456 |
* Get the outline shape of the text characters. |
383 |
return currentChars.length(); |
457 |
* This uses the StrokingTextPainter to get the outline |
384 |
} |
458 |
* shape since in theory it should be the same. |
|
|
459 |
* |
460 |
* @param node the text node |
461 |
* @return the outline shape of the text characters |
462 |
*/ |
463 |
public Shape getOutline(TextNode node) { |
464 |
return PROXY_PAINTER.getOutline(node); |
465 |
} |
466 |
|
385 |
|
467 |
/** |
386 |
private boolean isXShow() { |
468 |
* Get the bounds. |
387 |
return vertChanges == 0; |
469 |
* This uses the StrokingTextPainter to get the bounds |
388 |
} |
470 |
* since in theory it should be the same. |
|
|
471 |
* |
472 |
* @param node the text node |
473 |
* @return the bounds of the text |
474 |
*/ |
475 |
public Rectangle2D getBounds2D(TextNode node) { |
476 |
/* (todo) getBounds2D() is too slow |
477 |
* because it uses the StrokingTextPainter. We should implement this |
478 |
* method ourselves. */ |
479 |
return PROXY_PAINTER.getBounds2D(node); |
480 |
} |
481 |
|
389 |
|
482 |
/** |
390 |
private boolean isYShow() { |
483 |
* Get the geometry bounds. |
391 |
return horizChanges == 0; |
484 |
* This uses the StrokingTextPainter to get the bounds |
392 |
} |
485 |
* since in theory it should be the same. |
|
|
486 |
* @param node the text node |
487 |
* @return the bounds of the text |
488 |
*/ |
489 |
public Rectangle2D getGeometryBounds(TextNode node) { |
490 |
return PROXY_PAINTER.getGeometryBounds(node); |
491 |
} |
492 |
|
393 |
|
493 |
// Methods that have no purpose for PS |
394 |
public void paint(PSGraphics2D g2d, TextUtil textUtil, TextPaintInfo tpi) |
|
|
395 |
throws IOException { |
396 |
if (getRunLength() > 0) { |
397 |
if (log.isDebugEnabled()) { |
398 |
log.debug("Text run: " + currentChars); |
399 |
} |
400 |
textUtil.writeTextMatrix(this.textTransform); |
401 |
if (isXShow()) { |
402 |
log.debug("Horizontal text: xshow"); |
403 |
paintXYShow(g2d, textUtil, tpi.fillPaint, true, false); |
404 |
} else if (isYShow()) { |
405 |
log.debug("Vertical text: yshow"); |
406 |
paintXYShow(g2d, textUtil, tpi.fillPaint, false, true); |
407 |
} else { |
408 |
log.debug("Arbitrary text: xyshow"); |
409 |
paintXYShow(g2d, textUtil, tpi.fillPaint, true, true); |
410 |
} |
411 |
boolean stroke = (tpi.strokePaint != null) && (tpi.strokeStroke != null); |
412 |
if (stroke) { |
413 |
log.debug("Stroked glyph outlines"); |
414 |
paintStrokedGlyphs(g2d, textUtil, tpi.strokePaint, tpi.strokeStroke); |
415 |
} |
416 |
} |
417 |
} |
494 |
|
418 |
|
495 |
/** |
419 |
private void paintXYShow(PSGraphics2D g2d, TextUtil textUtil, Paint paint, |
496 |
* Get the mark. |
420 |
boolean x, boolean y) throws IOException { |
497 |
* This does nothing since the output is pdf and not interactive. |
421 |
PSGenerator gen = textUtil.gen; |
498 |
* @param node the text node |
422 |
char firstChar = this.currentChars.charAt(0); |
499 |
* @param pos the position |
423 |
//Font only has to be setup up before the first character |
500 |
* @param all select all |
424 |
Font f = textUtil.selectFontForChar(firstChar); |
501 |
* @return null |
425 |
char mapped = f.mapChar(firstChar); |
502 |
*/ |
426 |
textUtil.selectFont(f, mapped); |
503 |
public Mark getMark(TextNode node, int pos, boolean all) { |
427 |
textUtil.setCurrentFont(f, mapped); |
504 |
return null; |
428 |
applyColor(paint, gen); |
505 |
} |
|
|
506 |
|
429 |
|
507 |
/** |
430 |
StringBuffer sb = new StringBuffer(); |
508 |
* Select at. |
431 |
sb.append('('); |
509 |
* This does nothing since the output is pdf and not interactive. |
432 |
for (int i = 0, c = this.currentChars.length(); i < c; i++) { |
510 |
* @param x the x position |
433 |
char ch = this.currentChars.charAt(i); |
511 |
* @param y the y position |
434 |
mapped = f.mapChar(ch); |
512 |
* @param node the text node |
435 |
PSGenerator.escapeChar(mapped, sb); |
513 |
* @return null |
436 |
} |
514 |
*/ |
437 |
sb.append(')'); |
515 |
public Mark selectAt(double x, double y, TextNode node) { |
438 |
if (x || y) { |
516 |
return null; |
439 |
sb.append("\n["); |
517 |
} |
440 |
int idx = 0; |
|
|
441 |
Iterator iter = this.relativePositions.iterator(); |
442 |
while (iter.hasNext()) { |
443 |
Point2D pt = (Point2D)iter.next(); |
444 |
if (idx > 0) { |
445 |
if (x) { |
446 |
sb.append(format(gen, pt.getX())); |
447 |
} |
448 |
if (y) { |
449 |
if (x) { |
450 |
sb.append(' '); |
451 |
} |
452 |
sb.append(format(gen, -pt.getY())); |
453 |
} |
454 |
if (idx % 8 == 0) { |
455 |
sb.append('\n'); |
456 |
} else { |
457 |
sb.append(' '); |
458 |
} |
459 |
} |
460 |
idx++; |
461 |
} |
462 |
if (x) { |
463 |
sb.append('0'); |
464 |
} |
465 |
if (y) { |
466 |
if (x) { |
467 |
sb.append(' '); |
468 |
} |
469 |
sb.append('0'); |
470 |
} |
471 |
sb.append(']'); |
472 |
} |
473 |
sb.append(' '); |
474 |
if (x) { |
475 |
sb.append('x'); |
476 |
} |
477 |
if (y) { |
478 |
sb.append('y'); |
479 |
} |
480 |
sb.append("show"); // --> xshow, yshow or xyshow |
481 |
gen.writeln(sb.toString()); |
482 |
} |
518 |
|
483 |
|
519 |
/** |
484 |
private String format(PSGenerator gen, double coord) { |
520 |
* Select to. |
485 |
if (Math.abs(coord) < 0.00001) { |
521 |
* This does nothing since the output is pdf and not interactive. |
486 |
return "0"; |
522 |
* @param x the x position |
487 |
} else { |
523 |
* @param y the y position |
488 |
return gen.formatDouble5(coord); |
524 |
* @param beginMark the start mark |
489 |
} |
525 |
* @return null |
490 |
} |
526 |
*/ |
|
|
527 |
public Mark selectTo(double x, double y, Mark beginMark) { |
528 |
return null; |
529 |
} |
530 |
|
491 |
|
531 |
/** |
492 |
private void paintStrokedGlyphs(PSGraphics2D g2d, TextUtil textUtil, |
532 |
* Selec first. |
493 |
Paint strokePaint, Stroke stroke) throws IOException { |
533 |
* This does nothing since the output is pdf and not interactive. |
494 |
PSGenerator gen = textUtil.gen; |
534 |
* @param node the text node |
|
|
535 |
* @return null |
536 |
*/ |
537 |
public Mark selectFirst(TextNode node) { |
538 |
return null; |
539 |
} |
540 |
|
495 |
|
541 |
/** |
496 |
applyColor(strokePaint, gen); |
542 |
* Select last. |
497 |
PSGraphics2D.applyStroke(stroke, gen); |
543 |
* This does nothing since the output is pdf and not interactive. |
|
|
544 |
* @param node the text node |
545 |
* @return null |
546 |
*/ |
547 |
public Mark selectLast(TextNode node) { |
548 |
return null; |
549 |
} |
550 |
|
498 |
|
551 |
/** |
499 |
Font f = null; |
552 |
* Get selected. |
500 |
Iterator iter = this.relativePositions.iterator(); |
553 |
* This does nothing since the output is pdf and not interactive. |
501 |
iter.next(); |
554 |
* @param start the start mark |
502 |
Point2D pos = new Point2D.Double(0, 0); |
555 |
* @param finish the finish mark |
503 |
gen.writeln("0 0 M"); |
556 |
* @return null |
504 |
for (int i = 0, c = this.currentChars.length(); i < c; i++) { |
557 |
*/ |
505 |
char ch = this.currentChars.charAt(0); |
558 |
public int[] getSelected(Mark start, Mark finish) { |
506 |
if (i == 0) { |
559 |
return null; |
507 |
//Font only has to be setup up before the first character |
560 |
} |
508 |
f = textUtil.selectFontForChar(ch); |
|
|
509 |
} |
510 |
char mapped = f.mapChar(ch); |
511 |
if (i == 0) { |
512 |
textUtil.selectFont(f, mapped); |
513 |
textUtil.setCurrentFont(f, mapped); |
514 |
} |
515 |
mapped = f.mapChar(this.currentChars.charAt(i)); |
516 |
//add glyph outlines to current path |
517 |
char codepoint = (char)(mapped % 256); |
518 |
gen.write("(" + codepoint + ")"); |
519 |
gen.writeln(" false charpath"); |
561 |
|
520 |
|
562 |
/** |
521 |
if (iter.hasNext()) { |
563 |
* Get the highlighted shape. |
522 |
//Position for the next character |
564 |
* This does nothing since the output is pdf and not interactive. |
523 |
Point2D pt = (Point2D)iter.next(); |
565 |
* @param beginMark the start mark |
524 |
pos.setLocation(pos.getX() + pt.getX(), pos.getY() - pt.getY()); |
566 |
* @param endMark the end mark |
525 |
gen.writeln(gen.formatDouble5(pos.getX()) + " " |
567 |
* @return null |
526 |
+ gen.formatDouble5(pos.getY()) + " M"); |
568 |
*/ |
527 |
} |
569 |
public Shape getHighlightShape(Mark beginMark, Mark endMark) { |
528 |
} |
570 |
return null; |
529 |
gen.writeln("stroke"); //paints all accumulated glyph outlines |
|
|
530 |
} |
531 |
|
571 |
} |
532 |
} |
572 |
|
533 |
|
573 |
} |
534 |
} |