View Javadoc

1   /*
2    * VectorGraphics2D: Vector export for Java(R) Graphics2D
3    *
4    * (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
5    *
6    * This file is part of VectorGraphics2D.
7    *
8    * VectorGraphics2D is free software: you can redistribute it and/or modify
9    * it under the terms of the GNU Lesser General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or
11   * (at your option) any later version.
12   *
13   * VectorGraphics2D is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   * GNU Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with VectorGraphics2D.  If not, see <http://www.gnu.org/licenses/>.
20   */
21  
22  package de.erichseifert.vectorgraphics2d;
23  
24  import java.awt.AlphaComposite;
25  import java.awt.BasicStroke;
26  import java.awt.Color;
27  import java.awt.Composite;
28  import java.awt.Font;
29  import java.awt.FontMetrics;
30  import java.awt.Graphics;
31  import java.awt.Graphics2D;
32  import java.awt.GraphicsConfiguration;
33  import java.awt.Image;
34  import java.awt.MultipleGradientPaint;
35  import java.awt.Paint;
36  import java.awt.Rectangle;
37  import java.awt.RenderingHints;
38  import java.awt.Shape;
39  import java.awt.Stroke;
40  import java.awt.font.FontRenderContext;
41  import java.awt.font.GlyphVector;
42  import java.awt.font.TextLayout;
43  import java.awt.geom.AffineTransform;
44  import java.awt.geom.Arc2D;
45  import java.awt.geom.Ellipse2D;
46  import java.awt.geom.Line2D;
47  import java.awt.geom.Path2D;
48  import java.awt.geom.Rectangle2D;
49  import java.awt.geom.RoundRectangle2D;
50  import java.awt.image.AffineTransformOp;
51  import java.awt.image.BufferedImage;
52  import java.awt.image.BufferedImageOp;
53  import java.awt.image.ImageObserver;
54  import java.awt.image.RenderedImage;
55  import java.awt.image.renderable.RenderableImage;
56  import java.io.UnsupportedEncodingException;
57  import java.text.AttributedCharacterIterator;
58  import java.util.HashMap;
59  import java.util.Locale;
60  import java.util.Map;
61  
62  public abstract class VectorGraphics2D extends Graphics2D {
63  	/** Constants to define how fonts are rendered. */
64  	public static enum FontRendering {
65  		/** Constant indicating that fonts should be rendered as text objects. */
66  		TEXT,
67  		/** Constant indicating that fonts should be converted to vectors. */
68  		VECTORS
69  	}
70  
71  	private final RenderingHints hints;
72  	private final StringBuffer document;
73  	private final Rectangle2D bounds;
74  
75  	private Color background;
76  	private Color color;
77  	private Shape clip;
78  	private Rectangle clipBounds;
79  	private Composite composite;
80  	private GraphicsConfiguration deviceConfig;
81  	private Font font;
82  	private FontMetrics fontMetrics;
83  	private final FontRenderContext fontRenderContext;
84  	private Paint paint;
85  	private Stroke stroke;
86  	private final AffineTransform transform;
87  	private boolean transformed;
88  	private Color xorMode;
89  
90  	private FontRendering fontRendering;
91  
92  	/**
93  	 * Constructor to initialize a new {@code VectorGraphics2D} document.
94  	 * The dimensions of the document must be passed.
95  	 * @param x Horizontal position of document origin.
96  	 * @param y Vertical position of document origin.
97  	 * @param width Width of document.
98  	 * @param height Height of document.
99  	 */
100 	public VectorGraphics2D(double x, double y, double width, double height) {
101 		hints = new RenderingHints(new HashMap<RenderingHints.Key, Object>());
102 		document = new StringBuffer();
103 		bounds = new Rectangle2D.Double(x, y, width, height);
104 		fontRendering = FontRendering.TEXT;
105 
106 		background = Color.WHITE;
107 		color = Color.BLACK;
108 		composite = AlphaComposite.getInstance(AlphaComposite.CLEAR);
109 		font = Font.decode(null);
110 		fontRenderContext = new FontRenderContext(null, false, true);
111 		paint = color;
112 		stroke = new BasicStroke(1f);
113 		transform = new AffineTransform();
114 		transformed = false;
115 		xorMode = Color.BLACK;
116 	}
117 
118 	@Override
119 	public void addRenderingHints(Map<?,?> hints) {
120 		this.hints.putAll(hints);
121 	}
122 
123 	@Override
124 	public void clip(Shape s) {
125 		// TODO
126 	}
127 
128 	@Override
129 	public void draw(Shape s) {
130 		writeShape(s);
131 		writeClosingDraw();
132 	}
133 
134 	@Override
135 	public void drawGlyphVector(GlyphVector g, float x, float y) {
136 		draw(g.getOutline(x, y));
137 	}
138 
139 	@Override
140 	public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
141 		BufferedImage bimg = getTransformedImage(img, xform);
142 		drawImage(bimg, null, bimg.getMinX(), bimg.getMinY());
143 		return true;
144 	}
145 
146 	@Override
147 	public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
148 		if (op != null) {
149 			img = op.filter(img, null);
150 		}
151 		drawImage(img, x, y, img.getWidth(), img.getHeight(), null);
152 	}
153 
154 	@Override
155 	public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
156 		drawRenderedImage(img.createDefaultRendering(), xform);
157 	}
158 
159 	@Override
160 	public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
161 		// TODO
162 	}
163 
164 	@Override
165 	public void drawString(String str, int x, int y) {
166 		drawString(str, (float)x, (float)y);
167 	}
168 
169 	@Override
170 	public void drawString(String str, float x, float y) {
171 		switch (getFontRendering()) {
172 		case VECTORS:
173 			TextLayout layout = new TextLayout(str, getFont(), getFontRenderContext());
174 			Shape s = layout.getOutline(AffineTransform.getTranslateInstance(x, y));
175 			fill(s);
176 			break;
177 		case TEXT:
178 			writeString(str, x, y);
179 			break;
180 		}
181 	}
182 
183 	@Override
184 	public void drawString(AttributedCharacterIterator iterator, int x, int y) {
185 		drawString(iterator, (float)x, (float)y);
186 	}
187 
188 	@Override
189 	public void drawString(AttributedCharacterIterator iterator, float x, float y) {
190 		// TODO Take text formatting into account
191 		StringBuffer buf = new StringBuffer();
192 		for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; c = iterator.next()) {
193 			buf.append(c);
194 		}
195 		drawString(buf.toString(), x, y);
196 	}
197 
198 	@Override
199 	public void fill(Shape s) {
200 		writeShape(s);
201 		writeClosingFill();
202 	}
203 
204 	@Override
205 	public Color getBackground() {
206 		return background;
207 	}
208 
209 	@Override
210 	public Composite getComposite() {
211 		return composite;
212 	}
213 
214 	@Override
215 	public GraphicsConfiguration getDeviceConfiguration() {
216 		return deviceConfig;
217 	}
218 
219 	@Override
220 	public FontRenderContext getFontRenderContext() {
221 		return fontRenderContext;
222 	}
223 
224 	@Override
225 	public Paint getPaint() {
226 		return paint;
227 	}
228 
229 	@Override
230 	public Object getRenderingHint(RenderingHints.Key hintKey) {
231 		if (RenderingHints.KEY_ANTIALIASING.equals(hintKey)) {
232 			return RenderingHints.VALUE_ANTIALIAS_OFF;
233 		} else if (RenderingHints.KEY_TEXT_ANTIALIASING.equals(hintKey)) {
234 			return RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
235 		} else if (RenderingHints.KEY_FRACTIONALMETRICS.equals(hintKey)) {
236 			return RenderingHints.VALUE_FRACTIONALMETRICS_ON;
237 		}
238 		return hints.get(hintKey);
239 	}
240 
241 	@Override
242 	public RenderingHints getRenderingHints() {
243 		return hints;
244 	}
245 
246 	@Override
247 	public Stroke getStroke() {
248 		return stroke;
249 	}
250 
251 	@Override
252 	public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
253 		if (onStroke) {
254 			Shape sStroke = getStroke().createStrokedShape(s);
255 			return sStroke.intersects(rect);
256 		} else  {
257 			return s.intersects(rect);
258 		}
259 	}
260 
261 	@Override
262 	public void setBackground(Color color) {
263 		background = color;
264 	}
265 
266 	@Override
267 	public void setComposite(Composite comp) {
268 		composite = comp;
269 	}
270 
271 	@Override
272 	public void setPaint(Paint paint) {
273 		if (paint != null) {
274 			this.paint = paint;
275 			if (paint instanceof Color) {
276 				setColor((Color) paint);
277 			} else if (paint instanceof MultipleGradientPaint) {
278 				// Set brightest or least opaque color for gradients
279 				Color[] colors = ((MultipleGradientPaint)paint).getColors();
280 				if (colors.length == 1) {
281 					setColor(colors[0]);
282 				} else if (colors.length > 1) {
283 					Color colLight = colors[0];
284 					float brightness = getBrightness(colLight);
285 					int alpha = colLight.getAlpha();
286 
287 					for (int i = 1; i < colors.length; i++) {
288 						Color c = colors[i];
289 						float b = getBrightness(c);
290 						int a = c.getAlpha();
291 						if (b < brightness || a < alpha) {
292 							colLight = c;
293 							brightness = b;
294 						}
295 					}
296 					setColor(colLight);
297 				}
298 			}
299 		}
300 	}
301 
302 	/**
303 	 * Utility method to get the brightness of a specified color.
304 	 * @param c Color.
305 	 * @return Brightness value between 0f (black) and 1f (white).
306 	 */
307 	private static float getBrightness(Color c) {
308 		return Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null)[2];
309 	}
310 
311 	@Override
312 	public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
313 		hints.put(hintKey, hintValue);
314 	}
315 
316 	@Override
317 	public void setRenderingHints(Map<?, ?> hints) {
318 		this.hints.putAll(hints);
319 	}
320 
321 	@Override
322 	public void setStroke(Stroke s) {
323 		stroke = s;
324 	}
325 
326 	@Override
327 	public AffineTransform getTransform() {
328 		return new AffineTransform(transform);
329 	}
330 
331 	@Override
332 	public void setTransform(AffineTransform tx) {
333 		setAffineTransform(tx);
334 	}
335 
336 	protected void setAffineTransform(AffineTransform tx) {
337 		if (!transform.equals(tx)) {
338 			transform.setTransform(tx);
339 			transformed = true;
340 		}
341 	}
342 
343 	@Override
344 	public void shear(double shx, double shy) {
345 		AffineTransform transform = getTransform();
346 		transform.shear(shx, shy);
347 		setAffineTransform(transform);
348 	}
349 
350 	@Override
351 	public void transform(AffineTransform tx) {
352 		AffineTransform transform = getTransform();
353 		transform.concatenate(tx);
354 		setAffineTransform(transform);
355 	}
356 
357 	@Override
358 	public void translate(int x, int y) {
359 		translate((double)x, (double)y);
360 	}
361 
362 	@Override
363 	public void translate(double tx, double ty) {
364 		AffineTransform transform = getTransform();
365 		transform.translate(tx, ty);
366 		setAffineTransform(transform);
367 	}
368 
369 	@Override
370 	public void rotate(double theta) {
371 		AffineTransform transform = getTransform();
372 		transform.rotate(theta);
373 		setAffineTransform(transform);
374 	}
375 
376 	@Override
377 	public void rotate(double theta, double x, double y) {
378 		AffineTransform transform = getTransform();
379 		transform.rotate(theta, x, y);
380 		setAffineTransform(transform);
381 	}
382 
383 	@Override
384 	public void scale(double sx, double sy) {
385 		AffineTransform transform = getTransform();
386 		transform.scale(sx, sy);
387 		setAffineTransform(transform);
388 	}
389 
390 	@Override
391 	public void clearRect(int x, int y, int width, int height) {
392 		// TODO Auto-generated method stub
393 	}
394 
395 	@Override
396 	public void clipRect(int x, int y, int width, int height) {
397 		// TODO Auto-generated method stub
398 	}
399 
400 	@Override
401 	public void copyArea(int x, int y, int width, int height, int dx, int dy) {
402 		// TODO Auto-generated method stub
403 	}
404 
405 	@Override
406 	public Graphics create() {
407 		return this;
408 	}
409 
410 	@Override
411 	public void dispose() {
412 	}
413 
414 	@Override
415 	public void drawArc(int x, int y, int width, int height, int startAngle,
416 			int arcAngle) {
417 		draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN));
418 	}
419 
420 	@Override
421 	public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
422 		return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer);
423 	}
424 
425 	@Override
426 	public boolean drawImage(Image img, int x, int y, Color bgcolor,
427 			ImageObserver observer) {
428 		return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer);
429 	}
430 
431 	@Override
432 	public boolean drawImage(Image img, int x, int y, int width, int height,
433 			ImageObserver observer) {
434 		int imgWidth = img.getWidth(observer);
435 		int imgHeight = img.getHeight(observer);
436 		writeImage(img, imgWidth, imgHeight, x, y, width, height);
437 		return true;  // TODO Return only true if image data was complete
438 	}
439 
440 	@Override
441 	public boolean drawImage(Image img, int x, int y, int width, int height,
442 			Color bgcolor, ImageObserver observer) {
443 		return drawImage(img, x, y, width, height, observer);
444 	}
445 
446 	@Override
447 	public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
448 			int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
449 		// TODO Auto-generated method stub
450 		return false;
451 	}
452 
453 	@Override
454 	public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
455 			int sx1, int sy1, int sx2, int sy2, Color bgcolor,
456 			ImageObserver observer) {
457 		// TODO Auto-generated method stub
458 		return false;
459 	}
460 
461 	@Override
462 	public void drawLine(int x1, int y1, int x2, int y2) {
463 		draw(new Line2D.Double(x1, y1, x2, y2));
464 	}
465 
466 	@Override
467 	public void drawOval(int x, int y, int width, int height) {
468 		draw(new Ellipse2D.Double(x, y, width, height));
469 	}
470 
471 	@Override
472 	public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
473 		Path2D p = new Path2D.Float();
474 		for (int i = 0; i < nPoints; i++) {
475 			if (i > 0) {
476 				p.lineTo(xPoints[i], yPoints[i]);
477 			} else {
478 				p.moveTo(xPoints[i], yPoints[i]);
479 			}
480 		}
481 		p.closePath();
482 		draw(p);
483 	}
484 
485 	@Override
486 	public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
487 		Path2D p = new Path2D.Float();
488 		for (int i = 0; i < nPoints; i++) {
489 			if (i > 0) {
490 				p.lineTo(xPoints[i], yPoints[i]);
491 			} else {
492 				p.moveTo(xPoints[i], yPoints[i]);
493 			}
494 		}
495 		draw(p);
496 	}
497 
498 	@Override
499 	public void drawRect(int x, int y, int width, int height) {
500 		draw(new Rectangle2D.Double(x, y, width, height));
501 	}
502 
503 	@Override
504 	public void drawRoundRect(int x, int y, int width, int height,
505 			int arcWidth, int arcHeight) {
506 		draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
507 	}
508 
509 	@Override
510 	public void fillArc(int x, int y, int width, int height, int startAngle,
511 			int arcAngle) {
512 		fill(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE));
513 	}
514 
515 	@Override
516 	public void fillOval(int x, int y, int width, int height) {
517 		fill(new Ellipse2D.Double(x, y, width, height));
518 	}
519 
520 	@Override
521 	public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
522 		Path2D p = new Path2D.Float();
523 		for (int i = 0; i < nPoints; i++) {
524 			if (i > 0) {
525 				p.lineTo(xPoints[i], yPoints[i]);
526 			} else {
527 				p.moveTo(xPoints[i], yPoints[i]);
528 			}
529 		}
530 		p.closePath();
531 
532 		fill(p);
533 	}
534 
535 	@Override
536 	public void fillRect(int x, int y, int width, int height) {
537 		fill(new Rectangle2D.Double(x, y, width, height));
538 	}
539 
540 	@Override
541 	public void fillRoundRect(int x, int y, int width, int height,
542 			int arcWidth, int arcHeight) {
543 		fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
544 	}
545 
546 	@Override
547 	public Shape getClip() {
548 		return clip;
549 	}
550 
551 	@Override
552 	public Rectangle getClipBounds() {
553 		return clipBounds;
554 	}
555 
556 	@Override
557 	public Color getColor() {
558 		return color;
559 	}
560 
561 	@Override
562 	public Font getFont() {
563 		return font;
564 	}
565 
566 	@Override
567 	public FontMetrics getFontMetrics(Font f) {
568 		return fontMetrics;
569 	}
570 
571 	@Override
572 	public void setClip(Shape clip) {
573 		this.clip = clip;
574 	}
575 
576 	@Override
577 	public void setClip(int x, int y, int width, int height) {
578 		clip = new Rectangle(x, y, width, height);
579 	}
580 
581 	@Override
582 	public void setColor(Color c) {
583 		color = c;
584 	}
585 
586 	@Override
587 	public void setFont(Font font) {
588 		if (!this.font.equals(font)) {
589 			this.font = font;
590 		}
591 	}
592 
593 	@Override
594 	public void setPaintMode() {
595 		// TODO Auto-generated method stub
596 
597 	}
598 
599 	@Override
600 	public void setXORMode(Color c1) {
601 		xorMode = c1;
602 	}
603 
604 	/**
605 	 * Utility method for writing multiple objects to the SVG document.
606 	 * @param strs Objects to be written
607 	 */
608 	protected void write(Object... strs) {
609 		for (Object o : strs) {
610 			String str = o.toString();
611 			if ((o instanceof Double) || (o instanceof Float)) {
612 				str = String.format(Locale.ENGLISH, "%.7f", o).replaceAll("\\.?0+$", "");
613 			}
614 			document.append(str);
615 		}
616 	}
617 
618 	/**
619 	 * Utility method for writing a line of multiple objects to the SVG document.
620 	 * @param strs Objects to be written
621 	 */
622 	protected void writeln(Object... strs) {
623 		write(strs);
624 		write("\n");
625 	}
626 
627 	/**
628 	 * Write the specified shape to the document. This does not necessarily
629 	 * contain the actual command to paint the shape.
630 	 * @param s Shape to be written.
631 	 */
632 	protected abstract void writeShape(Shape s);
633 
634 	/**
635 	 * Write the specified image to the document. A number of dimensions will
636 	 * specify how the image will be placed in the document.
637 	 * @param img Image to be rendered.
638 	 * @param imgWidth Number of pixels in horizontal direction.
639 	 * @param imgHeight Number of pixels in vertical direction
640 	 * @param x Horizontal position in document units where the upper left corner of the image should be placed.
641 	 * @param y Vertical position in document units where the upper left corner of the image should be placed.
642 	 * @param width Width of the image in document units.
643 	 * @param height Height of the image in document units.
644 	 */
645 	protected abstract void writeImage(Image img, int imgWidth, int imgHeight, double x, double y, double width, double height);
646 
647 	/**
648 	 * Write a text string to the document at a specified position.
649 	 * @param str Text to be rendered.
650 	 * @param x Horizontal position in document units.
651 	 * @param y Vertical position in document units.
652 	 */
653 	protected abstract void writeString(String str, double x, double y);
654 
655 	/**
656 	 * Write a command to draw the outline of a previously inserted shape.
657 	 */
658 	protected abstract void writeClosingDraw();
659 
660 	/**
661 	 * Write a command to fill the outline of a previously inserted shape.
662 	 */
663 	protected abstract void writeClosingFill();
664 
665 	/**
666 	 * Write the header to start a new document.
667 	 */
668 	protected abstract void writeHeader();
669 
670 	/**
671 	 * Returns a string of the footer to end a document.
672 	 */
673 	protected abstract String getFooter();
674 
675 	private BufferedImage getTransformedImage(Image image, AffineTransform xform) {
676 		Integer interpolationType = (Integer)hints.get(RenderingHints.KEY_INTERPOLATION);
677 		if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(interpolationType)) {
678 			interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
679 		} else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(interpolationType)) {
680 			interpolationType = AffineTransformOp.TYPE_BILINEAR;
681 		} else {
682 			interpolationType = AffineTransformOp.TYPE_BICUBIC;
683 		}
684 		AffineTransformOp op = new AffineTransformOp(xform, interpolationType);
685 		BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image);
686 		return op.filter(bufferedImage, null);
687 	}
688 
689 	/**
690 	 * Returns whether a distorting transformation has been applied to the
691 	 * document.
692 	 * @return <code>true</code> if the document is distorted, otherwise <code>false</code>.
693 	 */
694 	protected boolean isDistorted() {
695 		if (!isTransformed()) {
696 			return false;
697 		}
698 		int type = transform.getType();
699 		int otherButTranslatedOrScaled = ~(AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_MASK_SCALE);
700 		return (type & otherButTranslatedOrScaled) != 0;
701 	}
702 
703 	@Override
704 	public String toString() {
705 		return document.toString() + getFooter();
706 	}
707 
708 	/**
709 	 * Encodes the painted data into a sequence of bytes.
710 	 * @return A byte array containing the data in the current file format.
711 	 */
712 	public byte[] getBytes() {
713 		try {
714 			return toString().getBytes("UTF-8");
715 		} catch (UnsupportedEncodingException e) {
716 			return toString().getBytes();
717 		}
718 	}
719 
720 	/**
721 	 * Returns the dimensions of the document.
722 	 * @return dimensions of the document.
723 	 */
724 	public Rectangle2D getBounds() {
725 		Rectangle2D b = new Rectangle2D.Double();
726 		b.setFrame(bounds);
727 		return b;
728 	}
729 
730 	/**
731 	 * Returns the number of bytes of the document.
732 	 * @return size of the document in bytes.
733 	 */
734 	protected int size() {
735 		return document.length();
736 	}
737 
738 	/**
739 	 * Returns how fonts should be rendered.
740 	 * @return Font rendering mode.
741 	 */
742 	public FontRendering getFontRendering() {
743 		return fontRendering;
744 	}
745 
746 	/**
747 	 * Sets how fonts should be rendered. For example, they can be converted
748 	 * to vector shapes.
749 	 * @param mode New font rendering mode.
750 	 */
751 	public void setFontRendering(FontRendering mode) {
752 		fontRendering = mode;
753 	}
754 
755 	/**
756 	 * Returns whether an affine transformation like translation, scaling,
757 	 * rotation or shearing has been applied to this graphics instance.
758 	 * @return <code>true</code> if the instance has been transformed,
759 	 *         <code>false</code> otherwise
760 	 */
761 	protected boolean isTransformed() {
762 		return transformed;
763 	}
764 
765 }