summaryrefslogtreecommitdiff
path: root/gnu/java
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/java')
-rw-r--r--gnu/java/awt/java2d/AbstractGraphics2D.java469
-rw-r--r--gnu/java/awt/java2d/AlphaCompositeContext.java356
-rw-r--r--gnu/java/awt/peer/gtk/GdkGraphics2D.java5
-rw-r--r--gnu/java/nio/ChannelReader.java206
-rw-r--r--gnu/java/nio/ChannelWriter.java190
5 files changed, 1056 insertions, 170 deletions
diff --git a/gnu/java/awt/java2d/AbstractGraphics2D.java b/gnu/java/awt/java2d/AbstractGraphics2D.java
index 7338a327e..845c2206e 100644
--- a/gnu/java/awt/java2d/AbstractGraphics2D.java
+++ b/gnu/java/awt/java2d/AbstractGraphics2D.java
@@ -42,14 +42,12 @@ import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
-import java.awt.CompositeContext;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
-import java.awt.PaintContext;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
@@ -78,6 +76,7 @@ import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -92,6 +91,13 @@ public abstract class AbstractGraphics2D
{
/**
+ * Accuracy of the sampling in the anti-aliasing shape filler.
+ * Lower values give more speed, while higher values give more quality.
+ * It is advisable to choose powers of two.
+ */
+ private static final int AA_SAMPLING = 8;
+
+ /**
* The transformation for this Graphics2D instance
*/
private AffineTransform transform;
@@ -132,24 +138,25 @@ public abstract class AbstractGraphics2D
private RenderingHints renderingHints;
/**
- * The paint context to use for draw and fill operations.
+ * The paint raster.
*/
- private PaintContext paintContext;
+ private Raster paintRaster;
/**
- * The paint raster.
+ * A cached pixel array.
*/
- private Raster paintRaster;
+ private int[] pixel;
/**
- * The composite context to use for draw and fill operations.
+ * Stores the alpha values for a scanline in the anti-aliasing shape
+ * renderer.
*/
- private CompositeContext compositeContext;
+ private transient int[] alpha;
/**
- * A cached pixel array.
+ * The edge table for the scanline conversion algorithms.
*/
- private int[] pixel;
+ private transient ArrayList[] edgeTable;
/**
* Indicates if cerain graphics primitives can be rendered in an optimized
@@ -176,8 +183,12 @@ public abstract class AbstractGraphics2D
background = Color.WHITE;
composite = AlphaComposite.SrcOver;
stroke = new BasicStroke();
- renderingHints = new RenderingHints(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_DEFAULT);
+ HashMap hints = new HashMap();
+ hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
+ hints.put(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_DEFAULT);
+ renderingHints = new RenderingHints(hints);
pixel = new int[4];
}
@@ -190,15 +201,18 @@ public abstract class AbstractGraphics2D
*/
public void draw(Shape shape)
{
- // Clip the shape.
- Shape clipped = clipShape(shape);
- if (clipped != null)
- {
- // Stroke the shape.
- Shape strokedShape = stroke.createStrokedShape(clipped);
- // Fill the shape.
- fillShape(strokedShape);
- }
+ // Stroke the shape.
+ Shape strokedShape = stroke.createStrokedShape(shape);
+
+ // Clip the stroked shape.
+// Shape clipped = clipShape(strokedShape);
+// if (clipped != null)
+// {
+// // Fill the shape.
+// fillShape(clipped, false);
+// }
+ // FIXME: Clipping doesn't seem to work.
+ fillShape(strokedShape, false);
}
public boolean drawImage(Image image, AffineTransform xform, ImageObserver obs)
@@ -290,9 +304,10 @@ public abstract class AbstractGraphics2D
*/
public void fill(Shape shape)
{
- Shape clipped = clipShape(shape);
- if (clipped != null)
- fillShape(shape);
+// Shape clipped = clipShape(shape);
+// if (clipped != null)
+// fillShape(clipped, false);
+ fillShape(shape, false);
}
public boolean hit(Rectangle rect, Shape text, boolean onStroke)
@@ -309,9 +324,6 @@ public abstract class AbstractGraphics2D
public void setComposite(Composite comp)
{
composite = comp;
- compositeContext = composite.createContext(getColorModel(),
- getDestinationColorModel(),
- renderingHints);
if (! (comp.equals(AlphaComposite.SrcOver)))
isOptimized = false;
else
@@ -325,14 +337,17 @@ public abstract class AbstractGraphics2D
*/
public void setPaint(Paint p)
{
- paint = p;
-
- if (paint != null && ! (paint instanceof Color))
- isOptimized = false;
- else
+ if (p != null)
{
- updateOptimization();
- rawSetForeground((Color) paint);
+ paint = p;
+
+ if (! (paint instanceof Color))
+ isOptimized = false;
+ else
+ {
+ updateOptimization();
+ rawSetForeground((Color) paint);
+ }
}
}
@@ -423,6 +438,7 @@ public abstract class AbstractGraphics2D
Rectangle r = (Rectangle) clip;
r.x -= x;
r.y -= y;
+ setClip(r);
}
else
{
@@ -660,6 +676,8 @@ public abstract class AbstractGraphics2D
Rectangle clipRect = (Rectangle) clip;
Rectangle r = (Rectangle) s;
computeIntersection(r.x, r.y, r.width, r.height, clipRect);
+ // Call setClip so that subclasses get notified.
+ setClip(clipRect);
}
else
{
@@ -678,6 +696,8 @@ public abstract class AbstractGraphics2D
current.intersect(intersect);
clip = current;
isOptimized = false;
+ // Call setClip so that subclasses get notified.
+ setClip(clip);
}
}
@@ -700,14 +720,18 @@ public abstract class AbstractGraphics2D
AffineTransform t = new AffineTransform();
t.translate(x, y);
- // TODO: We could use fill(gv.getOutline()), but that doesn't seem
- // to work yet with the font infrastructure I use.
+// // TODO: We could use fill(gv.getOutline()), but that seems to be
+ // slightly more inefficient.
for (int i = 0; i < numGlyphs; i++)
{
//fill(gv.getGlyphVisualBounds(i));
GeneralPath p = new GeneralPath(gv.getGlyphOutline(i));
p.transform(t);
- fill(p);
+ //Shape clipped = clipShape(p);
+ //if (clipped != null)
+ // fillShape(clipped, true);
+ // FIXME: Clipping doesn't seem to work correctly.
+ fillShape(p, true);
}
}
@@ -828,7 +852,10 @@ public abstract class AbstractGraphics2D
*/
public Rectangle getClipBounds()
{
- return clip.getBounds();
+ Rectangle b = null;
+ if (clip != null)
+ b = clip.getBounds();
+ return b;
}
/**
@@ -896,7 +923,11 @@ public abstract class AbstractGraphics2D
public void drawLine(int x1, int y1, int x2, int y2)
{
if (isOptimized)
- rawDrawLine(x1, y1, x2, y2);
+ {
+ int tx = (int) transform.getTranslateX();
+ int ty = (int) transform.getTranslateY();
+ rawDrawLine(x1 + tx, y1 + ty, x2 + tx, y2 + ty);
+ }
else
{
Line2D line = new Line2D.Double(x1, y1, x2, y2);
@@ -915,7 +946,11 @@ public abstract class AbstractGraphics2D
public void fillRect(int x, int y, int width, int height)
{
if (isOptimized)
- rawFillRect(x, y, width, height);
+ {
+ int tx = (int) transform.getTranslateX();
+ int ty = (int) transform.getTranslateY();
+ rawFillRect(x + tx, y + ty, width, height);
+ }
else
{
fill(new Rectangle(x, y, width, height));
@@ -1100,12 +1135,32 @@ public abstract class AbstractGraphics2D
* current clip.
*
* @param s the shape to fill
+ * @param isFont <code>true</code> if the shape is a font outline
*/
- protected void fillShape(Shape s)
+ protected void fillShape(Shape s, boolean isFont)
{
+ // Determine if we need to antialias stuff.
+ boolean antialias = false;
+ if (isFont)
+ {
+ Object v = renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
+ // We default to antialiasing on for text as long as we have no
+ // good hinting implemented.
+ antialias = (v == RenderingHints.VALUE_TEXT_ANTIALIAS_ON
+ || v == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
+ }
+ else
+ {
+ Object v = renderingHints.get(RenderingHints.KEY_ANTIALIASING);
+ antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ // Flatten the path. TODO: Determine the best flattening factor
+ // wrt to speed and quality.
PathIterator path = s.getPathIterator(getTransform(), 1.0);
+
// Build up polygons and let the native backend render this using
- // rawFillPolygon() which would provide a default implementation for
+ // rawFillShape() which would provide a default implementation for
// drawPixel using a PolyScan algorithm.
double[] seg = new double[6];
@@ -1154,7 +1209,12 @@ public abstract class AbstractGraphics2D
path.next();
}
if (segs.size() > 0)
- rawFillShape(segs, minX, minY, maxX, maxY);
+ {
+ if (antialias)
+ fillShapeAntialias(segs, minX, minY, maxX, maxY);
+ else
+ rawFillShape(segs, minX, minY, maxX, maxY);
+ }
}
/**
@@ -1193,6 +1253,11 @@ public abstract class AbstractGraphics2D
*/
protected abstract void rawSetForeground(Color c);
+ protected void rawSetForeground(int r, int g, int b)
+ {
+ rawSetForeground(new Color(r, g, b));
+ }
+
/**
* Returns the color model of this Graphics object.
*
@@ -1341,12 +1406,6 @@ public abstract class AbstractGraphics2D
for (Iterator i = segs.iterator(); i.hasNext();)
{
PolyEdge edge = (PolyEdge) i.next();
-
- // Horizontal edges are not needed and make things only more
- // complicated. Skip them.
- if (Math.ceil(edge.y0) == Math.ceil(edge.y1))
- continue;
-
int yindex = (int) ((int) Math.ceil(edge.y0) - (int) Math.ceil(minY));
if (edgeTable[yindex] == null) // Create bucket when needed.
edgeTable[yindex] = new ArrayList();
@@ -1376,7 +1435,7 @@ public abstract class AbstractGraphics2D
for (Iterator i = activeEdges.iterator(); i.hasNext();)
{
PolyEdge edge = (PolyEdge) i.next();
- if (y >= edge.y0 && y >= edge.y1)
+ if (y > edge.y1)
i.remove();
else
{
@@ -1416,43 +1475,309 @@ public abstract class AbstractGraphics2D
}
// Now draw all pixels inside the polygon.
- int x0 = 0; // Gets initialized in the first else branch below.
+ // This is the last edge that intersected the scanline.
+ PolyEdge previous = null; // Gets initialized below.
boolean active = false;
//System.err.println("scanline: " + y);
for (Iterator i = activeEdges.iterator(); i.hasNext();)
{
PolyEdge edge = (PolyEdge) i.next();
+ // Only fill scanline, if the current edge actually intersects
+ // the scanline. There may be edges that lie completely
+ // within the current scanline.
+ //System.err.println("previous: " + previous);
//System.err.println("edge: " + edge);
- int x = (int) edge.xIntersection;
if (active)
{
- fillScanline(x0, x, y);
- active = false;
+ if (edge.y1 > y)
+ {
+ int x0 = (int) previous.xIntersection;
+ int x1 = (int) edge.xIntersection;
+ fillScanline(x0, x1, y);
+ previous = edge;
+ active = false;
+ }
}
else
{
- x0 = x;
- active = true;
+ if (edge.y1 > y)
+ {
+ previous = edge;
+ active = true;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Paints a scanline between x0 and x1.
+ *
+ * @param x0 the left offset
+ * @param x1 the right offset
+ * @param y the scanline
+ */
+ protected void fillScanline(int x0, int x1, int y)
+ {
+ if (paint instanceof Color && composite == AlphaComposite.SrcOver)
+ {
+ rawDrawLine(x0, y, x1, y);
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+ }
+
+ /**
+ * Fills arbitrary shapes in an anti-aliased fashion.
+ *
+ * @param segs the line segments which define the shape which is to be filled
+ * @param minX the bounding box, left X
+ * @param minY the bounding box, upper Y
+ * @param maxX the bounding box, right X
+ * @param maxY the bounding box, lower Y
+ */
+ private void fillShapeAntialias(ArrayList segs, double minX, double minY,
+ double maxX, double maxY)
+ {
+ // This is an implementation of a polygon scanline conversion algorithm
+ // described here:
+ // http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/scan/
+ // The antialiasing is implemented using a sampling technique, we do
+ // not scan whole lines but fractions of the line.
+
+ // This array will contain the oversampled transparency values for
+ // each pixel in the scanline.
+ int numScanlines = (int) Math.ceil(maxY) - (int) minY;
+ int numScanlinePixels = (int) Math.ceil(maxX) - (int) minX + 1;
+ if (alpha == null || alpha.length < (numScanlinePixels + 1))
+ alpha = new int[numScanlinePixels + 1];
+
+ int firstLine = (int) minY;
+ //System.err.println("minY: " + minY);
+ int firstSubline = (int) (Math.ceil((minY - Math.floor(minY)) * AA_SAMPLING));
+ double firstLineDouble = firstLine + firstSubline / (double) AA_SAMPLING;
+ //System.err.println("firstSubline: " + firstSubline);
+
+ // Create table of all edges.
+ // The edge buckets, sorted and indexed by their Y values.
+ //System.err.println("numScanlines: " + numScanlines);
+ if (edgeTable == null
+ || edgeTable.length < numScanlines * AA_SAMPLING + AA_SAMPLING)
+ edgeTable = new ArrayList[numScanlines * AA_SAMPLING + AA_SAMPLING];
+
+ //System.err.println("firstLineDouble: " + firstLineDouble);
+
+ for (Iterator i = segs.iterator(); i.hasNext();)
+ {
+ PolyEdge edge = (PolyEdge) i.next();
+ int yindex = (int) (Math.ceil((edge.y0 - firstLineDouble) * AA_SAMPLING));
+ //System.err.println("yindex: " + yindex + " for y0: " + edge.y0);
+ // Initialize edge's slope and initial xIntersection.
+ edge.slope = ((edge.x1 - edge.x0) / (edge.y1 - edge.y0)) / AA_SAMPLING;
+ if (edge.y0 == edge.y1) // Horizontal edge.
+ edge.xIntersection = Math.min(edge.x0, edge.x1);
+ else
+ {
+ double alignedFirst = Math.ceil(edge.y0 * AA_SAMPLING) / AA_SAMPLING;
+ edge.xIntersection = edge.x0 + (edge.slope * AA_SAMPLING) * (alignedFirst - edge.y0);
+ }
+ //System.err.println(edge);
+ // FIXME: Sanity check should not be needed when clipping works.
+ if (yindex >= 0 && yindex < edgeTable.length)
+ {
+ if (edgeTable[yindex] == null) // Create bucket when needed.
+ edgeTable[yindex] = new ArrayList();
+ edgeTable[yindex].add(edge); // Add edge to the bucket of its line.
+ }
+ }
+
+ // The activeEdges list contains all the edges of the current scanline
+ // ordered by their intersection points with this scanline.
+ ArrayList activeEdges = new ArrayList();
+ PolyEdgeComparator comparator = new PolyEdgeComparator();
+
+ // Scan all lines.
+ int yindex = 0;
+ //System.err.println("firstLine: " + firstLine + ", maxY: " + maxY + ", firstSubline: " + firstSubline);
+ for (int y = firstLine; y <= maxY; y++)
+ {
+ for (int subY = firstSubline; subY < AA_SAMPLING; subY++)
+ {
+ //System.err.println("scanline: " + y + ", subScanline: " + subY);
+ ArrayList bucket = edgeTable[yindex];
+ // Update all the x intersections in the current activeEdges table
+ // and remove entries that are no longer in the scanline.
+ for (Iterator i = activeEdges.iterator(); i.hasNext();)
+ {
+ PolyEdge edge = (PolyEdge) i.next();
+ // TODO: Do the following using integer arithmetics.
+ if ((y + ((double) subY / (double) AA_SAMPLING)) > edge.y1)
+ i.remove();
+ else
+ {
+ edge.xIntersection += edge.slope;
+ //System.err.println("edge: " + edge);
+ //edge.xIntersection = edge.x0 + edge.slope * (y - edge.y0);
+ //System.err.println("edge.xIntersection: " + edge.xIntersection);
+ }
+ }
+
+ if (bucket != null)
+ {
+ activeEdges.addAll(bucket);
+ edgeTable[yindex].clear();
+ }
+
+ // Sort current edges. We are using a bubble sort, because the order
+ // of the intersections will not change in most situations. They
+ // will only change, when edges intersect each other.
+ int size = activeEdges.size();
+ if (size > 1)
+ {
+ for (int i = 1; i < size; i++)
+ {
+ PolyEdge e1 = (PolyEdge) activeEdges.get(i - 1);
+ PolyEdge e2 = (PolyEdge) activeEdges.get(i);
+ if (comparator.compare(e1, e2) > 0)
+ {
+ // Swap e2 with its left neighbor until it 'fits'.
+ int j = i;
+ do
+ {
+ activeEdges.set(j, e1);
+ activeEdges.set(j - 1, e2);
+ j--;
+ if (j >= 1)
+ e1 = (PolyEdge) activeEdges.get(j - 1);
+ } while (j >= 1 && comparator.compare(e1, e2) > 0);
+ }
+ }
+ }
+
+ // Now draw all pixels inside the polygon.
+ // This is the last edge that intersected the scanline.
+ PolyEdge previous = null; // Gets initialized below.
+ boolean active = false;
+ //System.err.println("scanline: " + y + ", subscanline: " + subY);
+ for (Iterator i = activeEdges.iterator(); i.hasNext();)
+ {
+ PolyEdge edge = (PolyEdge) i.next();
+ // Only fill scanline, if the current edge actually intersects
+ // the scanline. There may be edges that lie completely
+ // within the current scanline.
+ //System.err.println("previous: " + previous);
+ //System.err.println("edge: " + edge);
+ if (active)
+ {
+ // TODO: Use integer arithmetics here.
+ if (edge.y1 > (y + (subY / (double) AA_SAMPLING)))
+ {
+ //System.err.println(edge);
+ // TODO: Eliminate the aligments.
+ int x0 = (int) Math.min(Math.max(previous.xIntersection, minX), maxX);
+ int x1 = (int) Math.min(Math.max(edge.xIntersection, minX), maxX);
+ //System.err.println("minX: " + minX + ", x0: " + x0 + ", x1: " + x1 + ", maxX: " + maxX);
+ // TODO: Pull out cast.
+ alpha[x0 - (int) minX]++;
+ alpha[x1 - (int) minX + 1]--;
+ previous = edge;
+ active = false;
+ }
+ }
+ else
+ {
+ // TODO: Use integer arithmetics here.
+ if (edge.y1 > (y + (subY / (double) AA_SAMPLING)))
+ {
+ //System.err.println(edge);
+ previous = edge;
+ active = true;
+ }
+ }
}
+ yindex++;
}
+ firstSubline = 0;
+ // Render full scanline.
+ //System.err.println("scanline: " + y);
+ fillScanlineAA(alpha, (int) minX, (int) y, numScanlinePixels);
}
+ if (paint instanceof Color && composite == AlphaComposite.SrcOver)
+ rawSetForeground((Color) paint);
}
/**
- * Fills a horizontal line between x0 and x1 with the current paint and
- * composize. Backends should override this if they want to accelerate
- * general shape drawing. They should respect the current paint and
- * composite though (or call super for these tasks). It is not necessary
- * to clip the line.
+ * Fills a horizontal line between x0 and x1 for anti aliased rendering.
+ * the alpha array contains the deltas of the alpha values from one pixel
+ * to the next.
*
+ * @param alpha the alpha values in the scanline
* @param x0 the beginning of the scanline
- * @param x1 the end of the scanline
* @param y the y coordinate of the line
*/
- protected void fillScanline(int x0, int x1, int y)
+ private void fillScanlineAA(int[] alpha, int x0, int y, int numScanlinePixels)
{
- for (int x = x0; x < x1; x++)
- drawPixel(x, y);
+ int lastX = x0;
+ int lastAlpha = 0;
+ for (int i = 0; i < numScanlinePixels; i++)
+ {
+ if (alpha[i] == 0)
+ continue;
+ if (lastAlpha > 0)
+ {
+ //System.err.println("rawDrawScanline: " + lastX + ", " + (i+x0) + ", " + lastAlpha);
+ // TODO: Avoid double arithmetic.
+ fillScanlineAlpha(lastX, i + x0, y, lastAlpha);
+ }
+ lastAlpha += alpha[i];
+ alpha[i] = 0;
+ lastX = i + x0;
+ }
+ if (lastAlpha > 0)
+ // TODO: Avoid double arithmetic.
+ fillScanlineAlpha(lastX, x0 + numScanlinePixels, y, lastAlpha);
+ }
+
+ /**
+ * Renders one scanline with alpha sampling for anti-aliased shape rendering.
+ *
+ * @param x0 the start offset
+ * @param x1 the end offset
+ * @param y the scanline
+ * @param sample the sample value, relative to AA_SAMPLING
+ */
+ private void fillScanlineAlpha(int x0, int x1, int y, int sample)
+ {
+ if (paint instanceof Color && composite == AlphaComposite.SrcOver)
+ {
+ // FIXME: We should composite over current pixels, not over the
+ // background color.
+ Color fg = (Color) paint;
+ if (sample < AA_SAMPLING)
+ {
+ Color bg = background;
+ int bgShare = (AA_SAMPLING - sample);
+ int red = (fg.getRed() * sample + bg.getRed() * bgShare)
+ / AA_SAMPLING;
+ int green = (fg.getGreen() * sample + bg.getGreen() * bgShare)
+ / AA_SAMPLING;
+ int blue = (fg.getBlue() * sample + bg.getBlue() * bgShare)
+ / AA_SAMPLING;
+ rawSetForeground(red, green, blue);
+ fillScanline(x0, x1 - 1, y);
+ }
+ else
+ {
+ rawSetForeground(fg);
+ fillScanline(x0, x1 - 1, y);
+ }
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
}
/**
@@ -1464,6 +1789,10 @@ public abstract class AbstractGraphics2D
setPaint(Color.BLACK);
setFont(new Font("SansSerif", Font.PLAIN, 12));
isOptimized = true;
+
+ // FIXME: Should not be necessary. A clip of null should mean
+ // 'clip against device bounds.
+ clip = getDeviceBounds();
}
//protected abstract Raster getDestinationRaster(int x, int y, int w, int h);
@@ -1476,11 +1805,13 @@ public abstract class AbstractGraphics2D
private void updateOptimization()
{
int transformType = transform.getType();
- boolean optimizedTransform =
- (transformType &
- (AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_IDENTITY)) != 0;
+ boolean optimizedTransform = false;
+ if (transformType == AffineTransform.TYPE_IDENTITY
+ || transformType == AffineTransform.TYPE_TRANSLATION)
+ optimizedTransform = true;
- isOptimized = clip instanceof Rectangle
+ boolean optimizedClip = (clip == null || clip instanceof Rectangle);
+ isOptimized = optimizedClip
&& optimizedTransform && paint instanceof Color
&& composite == AlphaComposite.SrcOver
&& stroke.equals(new BasicStroke());
diff --git a/gnu/java/awt/java2d/AlphaCompositeContext.java b/gnu/java/awt/java2d/AlphaCompositeContext.java
new file mode 100644
index 000000000..e01296f2d
--- /dev/null
+++ b/gnu/java/awt/java2d/AlphaCompositeContext.java
@@ -0,0 +1,356 @@
+/* AlphaCompositeContext.java -- CompositeContext impl for AlphaComposite
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.java2d;
+
+import java.awt.AWTError;
+import java.awt.AlphaComposite;
+import java.awt.CompositeContext;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/**
+ * A CompositeContext implementation for {@link AlphaComposite}.
+ *
+ * @author Roman Kennke (kennke@aicas.com)
+ */
+public class AlphaCompositeContext
+ implements CompositeContext
+{
+
+ /**
+ * The Composite object for which we perform compositing.
+ */
+ private AlphaComposite composite;
+
+ /**
+ * The source color model.
+ */
+ private ColorModel srcColorModel;
+
+ /**
+ * The destination color model.
+ */
+ private ColorModel dstColorModel;
+
+ /**
+ * The blending factor for the source.
+ */
+ private float fs;
+
+ /**
+ * The blending factor for the destination.
+ */
+ private float fd;
+
+ /**
+ * Creates a new AlphaCompositeContext.
+ *
+ * @param aComp the AlphaComposite object
+ * @param srcCM the source color model
+ * @param dstCM the destination color model
+ */
+ public AlphaCompositeContext(AlphaComposite aComp, ColorModel srcCM,
+ ColorModel dstCM)
+ {
+ composite = aComp;
+ srcColorModel = srcCM;
+ dstColorModel = dstCM;
+
+
+ // Determine the blending factors according to the rule in the
+ // AlphaComposite. For some rules the factors must be determined
+ // dynamically because they depend on the actual pixel value.
+ switch (composite.getRule())
+ {
+ case AlphaComposite.CLEAR:
+ fs = 0.F;
+ fd= 0.F;
+ break;
+ case AlphaComposite.DST:
+ fs = 0.F;
+ fd= 1.F;
+ break;
+ case AlphaComposite.DST_ATOP:
+ fs = 1.F; // Determined later as 1 - alpha_dst;
+ fd = 1.F; // Determined later as alpha_src;
+ break;
+ case AlphaComposite.DST_IN:
+ fs = 0.F;
+ fd = 0.F; // Determined later as alpha_src;
+ break;
+ case AlphaComposite.DST_OUT:
+ fs = 0.F;
+ fd = 0.F; // Determined later as 1 - alpha_src;
+ break;
+ case AlphaComposite.DST_OVER:
+ fs = 1.F; // Determined later as 1 - alpha_dst.
+ fd= 1.F;
+ break;
+ case AlphaComposite.SRC:
+ fs = 1.F;
+ fd= 0.F;
+ break;
+ case AlphaComposite.SRC_ATOP:
+ fs = 1.F; // Determined later as alpha_dst;
+ fd = 1.F; // Determined later as 1 - alpha_src;
+ break;
+ case AlphaComposite.SRC_IN:
+ fs = 0.F; // Determined later as alpha_dst;
+ fd = 0.F;
+ break;
+ case AlphaComposite.SRC_OUT:
+ fs = 0.F; // Determined later as 1 - alpha_dst;
+ fd = 0.F;
+ break;
+ case AlphaComposite.SRC_OVER:
+ fs = 1.F;
+ fd= 1.F; // Determined later as 1 - alpha_src.
+ break;
+ case AlphaComposite.XOR:
+ fs = 1.F; // Determined later as 1 - alpha_dst.
+ fd= 1.F; // Determined later as 1 - alpha_src.
+ break;
+ default:
+ throw new AWTError("Illegal AlphaComposite rule");
+ }
+
+ }
+
+ /**
+ * Releases all resources held by this composite object.
+ */
+ public void dispose()
+ {
+ // Nothing to do here yet.
+ }
+
+ /**
+ * Performs compositing according to the rules specified in the
+ * AlphaComposite from the constructor.
+ */
+ public void compose(Raster src, Raster dstIn, WritableRaster dstOut)
+ {
+
+ // TODO: This implementation is very general and highly inefficient. There
+ // are two possible ways to optimize this:
+ // 1. Special cased implementations for common ColorModels and transfer
+ // types.
+ // 2. Native implementation.
+
+ int x0 = src.getMinX();
+ int y0 = src.getMinY();
+ int width = src.getWidth();
+ int height = src.getHeight();
+ int x1 = x0 + width;
+ int y1 = x0 + height;
+
+ Object srcPixel = null;
+ Object dstPixel = null;
+
+ // Prepare the array that holds the color and alpha components of the
+ // source pixels.
+ float[] srcComponents;
+ int srcComponentsLength = srcColorModel.getNumComponents();
+ if (! srcColorModel.hasAlpha())
+ srcComponentsLength += 1;
+ srcComponents = new float[srcComponentsLength];
+
+ // Prepare the array that holds the color and alpha components of the
+ // destination pixels.
+ float[] dstComponents;
+ int dstComponentsLength = dstColorModel.getNumComponents();
+ if (! dstColorModel.hasAlpha())
+ dstComponentsLength += 1;
+ dstComponents = new float[dstComponentsLength];
+
+ if (srcComponentsLength != dstComponentsLength)
+ throw new AWTError("The color models of the source and destination have"
+ + "incompatible number of color components");
+
+ int srcTransferType = srcColorModel.getTransferType();
+ int dstTransferType = dstColorModel.getTransferType();
+
+ for (int y = y0; y < y1; y++)
+ {
+ for (int x = x0; x < x1; x++)
+ {
+ // Fetch source pixel.
+ switch (srcTransferType)
+ {
+ case DataBuffer.TYPE_INT:
+ srcPixel = src.getPixel(x, y, (int[]) srcPixel);
+ break;
+ case DataBuffer.TYPE_FLOAT:
+ srcPixel = src.getPixel(x, y, (float[]) srcPixel);
+ break;
+ case DataBuffer.TYPE_DOUBLE:
+ srcPixel = src.getPixel(x, y, (double[]) srcPixel);
+ break;
+ default:
+ throw new AWTError("Invalid transfer type for source raster");
+ }
+ // Fetch destination pixel.
+ switch (dstTransferType)
+ {
+ case DataBuffer.TYPE_INT:
+ dstPixel = dstIn.getPixel(x, y, (int[]) dstPixel);
+ break;
+ case DataBuffer.TYPE_FLOAT:
+ dstPixel = dstIn.getPixel(x, y, (float[]) dstPixel);
+ break;
+ case DataBuffer.TYPE_DOUBLE:
+ dstPixel = dstIn.getPixel(x, y, (double[]) dstPixel);
+ break;
+ default:
+ throw new AWTError("Invalid transfer type for source raster");
+ }
+
+ // Get normalized components. This is the only type that is
+ // guaranteed to be supported by all ColorModels.
+ srcComponents =
+ srcColorModel.getNormalizedComponents(srcPixel, srcComponents, 0);
+ if (! srcColorModel.hasAlpha())
+ srcComponents[srcComponentsLength - 1] = 1.0F;
+ dstComponents =
+ dstColorModel.getNormalizedComponents(dstPixel, srcComponents, 0);
+ if (! dstColorModel.hasAlpha())
+ dstComponents[dstComponentsLength - 1] = 1.0F;
+
+ // Prepare the input.
+ float compositeAlpha = composite.getAlpha();
+ srcComponents[srcComponentsLength - 1] *= compositeAlpha;
+ if (srcColorModel.isAlphaPremultiplied())
+ {
+ for (int i = srcComponentsLength - 2; i >= 0; i++)
+ srcComponents[i] *= compositeAlpha;
+ }
+ else
+ {
+ for (int i = srcComponentsLength - 2; i >= 0; i++)
+ srcComponents[i] *= srcComponents[srcComponentsLength - 1];
+ }
+ if (! dstColorModel.isAlphaPremultiplied())
+ {
+ for (int i = dstComponentsLength - 2; i >= 0; i++)
+ dstComponents[i] *= dstComponents[dstComponents.length - 1];
+ }
+
+ // Determine the blending factors according to the rule in the
+ // AlphaComposite. For some rules the factors must be determined
+ // dynamically because they depend on the actual pixel value.
+ float srcAlpha = srcComponents[srcComponentsLength - 1];
+ float dstAlpha = dstComponents[dstComponentsLength - 1];
+ switch (composite.getRule())
+ {
+ case AlphaComposite.DST_ATOP:
+ fs = 1.F - dstAlpha;
+ fd = srcAlpha;
+ break;
+ case AlphaComposite.DST_IN:
+ fd = srcAlpha;
+ break;
+ case AlphaComposite.DST_OUT:
+ fd = 1.F - srcAlpha;
+ break;
+ case AlphaComposite.DST_OVER:
+ fs = 1.F - dstAlpha;
+ break;
+ case AlphaComposite.SRC_ATOP:
+ fs = srcAlpha;
+ fd = 1.F - srcAlpha;
+ break;
+ case AlphaComposite.SRC_IN:
+ fs = dstAlpha;
+ break;
+ case AlphaComposite.SRC_OUT:
+ fs = 1.F - dstAlpha;
+ break;
+ case AlphaComposite.SRC_OVER:
+ fd= 1.F - srcAlpha;
+ break;
+ case AlphaComposite.XOR:
+ fs = 1.F - dstAlpha;
+ fd= 1.F - srcAlpha;
+ break;
+ default:
+ // For the other cases the factors have already been determined
+ // in the constructor.
+ }
+
+ // Apply the blending equation to the pixels.
+ for (int i = 0; i < srcComponentsLength; i++)
+ {
+ dstComponents[i] = srcComponents[i] * fs
+ + dstComponents[i] * fd;
+ }
+
+ // Convert the result back when the destination is not
+ // alpha-premultiplied.
+ dstAlpha = dstComponents[dstComponentsLength - 1];
+ if (!dstColorModel.isAlphaPremultiplied() && dstAlpha != 0.F)
+ {
+ for (int i = 0; i < dstComponentsLength - 1; i++)
+ {
+ dstComponents[i] = dstComponents[i] / dstAlpha;
+ }
+ }
+
+ // Store the result in the destination raster.
+ dstPixel = dstColorModel.getDataElements(dstComponents, 0,
+ dstPixel);
+ switch (dstTransferType)
+ {
+ case DataBuffer.TYPE_INT:
+ dstOut.setPixel(x, y, (int[] ) dstPixel);
+ break;
+ case DataBuffer.TYPE_FLOAT:
+ dstOut.setPixel(x, y, (float[] ) dstPixel);
+ break;
+ case DataBuffer.TYPE_DOUBLE:
+ dstOut.setPixel(x, y, (double[] ) dstPixel);
+ break;
+ }
+
+ } // End X loop.
+ } // End Y loop.
+ }
+
+}
diff --git a/gnu/java/awt/peer/gtk/GdkGraphics2D.java b/gnu/java/awt/peer/gtk/GdkGraphics2D.java
index 195304dce..323d5614a 100644
--- a/gnu/java/awt/peer/gtk/GdkGraphics2D.java
+++ b/gnu/java/awt/peer/gtk/GdkGraphics2D.java
@@ -1229,7 +1229,10 @@ public class GdkGraphics2D extends Graphics2D
drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
updateBufferedImage();
-
+
+ // Cairo seems loosing the current color.
+ setColor(fg);
+
return true;
}
diff --git a/gnu/java/nio/ChannelReader.java b/gnu/java/nio/ChannelReader.java
index 44fe6625a..1e7372d2e 100644
--- a/gnu/java/nio/ChannelReader.java
+++ b/gnu/java/nio/ChannelReader.java
@@ -92,120 +92,126 @@ public class ChannelReader extends Reader
public int read(char[] buf, int offset, int count) throws IOException
{
- // I declared channel being null meaning that the reader is closed.
- if (!channel.isOpen())
- throw new IOException("Reader was already closed.");
-
- // I declared decoder being null meaning that there is no more data to read
- // and convert.
- if (decoder == null)
- return -1;
-
- // Stores the amount of character being read. It -1 so that if no conversion
- // occured the caller will see this as an 'end of file'.
- int sum = -1;
-
- // Copies any characters which may be left from the last invocation into the
- // destination array.
- if (charBuffer.remaining() > 0)
+ synchronized (lock)
{
- sum = Math.min(count, charBuffer.remaining());
- charBuffer.get(buf, offset, sum);
-
- // Updates the control variables according to the latest copy operation.
- offset += sum;
- count -= sum;
- }
-
- // Copies the character which have not been put in the destination array to
- // the beginning. If data is actually copied count will be 0. If no data is
- // copied count is >0 and we can now convert some more characters.
- charBuffer.compact();
-
- int converted = 0;
- boolean last = false;
-
- while (count != 0)
- {
- // Tries to convert some bytes (Which will intentionally fail in the
- // first place because we have not read any bytes yet.)
- CoderResult result = decoder.decode(byteBuffer, charBuffer, last);
- if (result.isMalformed() || result.isUnmappable())
- {
- // JDK throws exception when bytes are malformed for sure.
- // FIXME: Unsure what happens when a character is simply
- // unmappable.
- result.throwException();
- }
-
- // Marks that we should end this loop regardless whether the caller
- // wants more chars or not, when this was the last conversion.
- if (last)
+ // I declared channel being null meaning that the reader is closed.
+ if (!channel.isOpen())
+ throw new IOException("Reader was already closed.");
+
+ // I declared decoder being null meaning that there is no more data to read
+ // and convert.
+ if (decoder == null)
+ return -1;
+
+ // Stores the amount of character being read. It -1 so that if no conversion
+ // occured the caller will see this as an 'end of file'.
+ int sum = -1;
+
+ // Copies any characters which may be left from the last invocation into the
+ // destination array.
+ if (charBuffer.remaining() > 0)
{
- decoder = null;
+ sum = Math.min(count, charBuffer.remaining());
+ charBuffer.get(buf, offset, sum);
+
+ // Updates the control variables according to the latest copy operation.
+ offset += sum;
+ count -= sum;
}
- else if (result.isUnderflow())
+
+ // Copies the character which have not been put in the destination array to
+ // the beginning. If data is actually copied count will be 0. If no data is
+ // copied count is >0 and we can now convert some more characters.
+ charBuffer.compact();
+
+ int converted = 0;
+ boolean last = false;
+
+ while (count != 0)
{
- // We need more bytes to do the conversion.
-
- // Copies the not yet converted bytes to the beginning making it
- // being able to receive more bytes.
- byteBuffer.compact();
-
- // Reads in another bunch of bytes for being converted.
- if (channel.read(byteBuffer) == -1)
+ // Tries to convert some bytes (Which will intentionally fail in the
+ // first place because we have not read any bytes yet.)
+ CoderResult result = decoder.decode(byteBuffer, charBuffer, last);
+ if (result.isMalformed() || result.isUnmappable())
{
- // If there is no more data available in the channel we mark
- // that state for the final character conversion run which is
- // done in the next loop iteration.
- last = true;
+ // JDK throws exception when bytes are malformed for sure.
+ // FIXME: Unsure what happens when a character is simply
+ // unmappable.
+ result.throwException();
}
-
- // Prepares the byteBuffer for the next character conversion run.
- byteBuffer.flip();
+
+ // Marks that we should end this loop regardless whether the caller
+ // wants more chars or not, when this was the last conversion.
+ if (last)
+ {
+ decoder = null;
+ }
+ else if (result.isUnderflow())
+ {
+ // We need more bytes to do the conversion.
+
+ // Copies the not yet converted bytes to the beginning making it
+ // being able to receive more bytes.
+ byteBuffer.compact();
+
+ // Reads in another bunch of bytes for being converted.
+ if (channel.read(byteBuffer) == -1)
+ {
+ // If there is no more data available in the channel we mark
+ // that state for the final character conversion run which is
+ // done in the next loop iteration.
+ last = true;
+ }
+
+ // Prepares the byteBuffer for the next character conversion run.
+ byteBuffer.flip();
+ }
+
+ // Prepares the charBuffer for being drained.
+ charBuffer.flip();
+
+ converted = Math.min(count, charBuffer.remaining());
+ charBuffer.get(buf, offset, converted);
+
+ // Copies characters which have not yet being copied into the char-Array
+ // to the beginning making it possible to read them later (If data is
+ // really copied here, then the caller has received enough characters so
+ // far.).
+ charBuffer.compact();
+
+ // Updates the control variables according to the latest copy operation.
+ offset += converted;
+ count -= converted;
+
+ // Updates the amount of transferred characters.
+ sum += converted;
+
+ if (decoder == null)
+ {
+ break;
+ }
+
+ // Now that more characters have been transfered we let the loop decide
+ // what to do next.
}
-
- // Prepares the charBuffer for being drained.
+
+ // Makes the charBuffer ready for reading on the next invocation.
charBuffer.flip();
-
- converted = Math.min(count, charBuffer.remaining());
- charBuffer.get(buf, offset, converted);
-
- // Copies characters which have not yet being copied into the char-Array
- // to the beginning making it possible to read them later (If data is
- // really copied here, then the caller has received enough characters so
- // far.).
- charBuffer.compact();
-
- // Updates the control variables according to the latest copy operation.
- offset += converted;
- count -= converted;
-
- // Updates the amount of transferred characters.
- sum += converted;
-
- if (decoder == null)
- {
- break;
- }
-
- // Now that more characters have been transfered we let the loop decide
- // what to do next.
+
+ return sum;
}
-
- // Makes the charBuffer ready for reading on the next invocation.
- charBuffer.flip();
-
- return sum;
}
public void close() throws IOException
{
- channel.close();
+ synchronized (lock)
+ {
+ channel.close();
- // Makes sure all intermediate data is released by the decoder.
- if (decoder != null)
- decoder.reset();
+ // Makes sure all intermediate data is released by the decoder.
+ if (decoder != null)
+ decoder.reset();
+ }
}
}
diff --git a/gnu/java/nio/ChannelWriter.java b/gnu/java/nio/ChannelWriter.java
new file mode 100644
index 000000000..8e533ccbf
--- /dev/null
+++ b/gnu/java/nio/ChannelWriter.java
@@ -0,0 +1,190 @@
+/* ChannelWriter.java -- nio / writer bridge
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.nio;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+
+/**
+ * A Writer implementation that works by wrapping an NIO channel.
+ */
+public class ChannelWriter
+ extends Writer
+{
+ private static final int DEFAULT_BUFFER_CAP = 8192;
+
+ /**
+ * The output channel.
+ */
+ private WritableByteChannel byteChannel;
+
+ /**
+ * The encoder to use.
+ */
+ private CharsetEncoder enc;
+
+ /**
+ * The byte buffer. Translated characters are stored here on their way out.
+ */
+ private ByteBuffer byteBuffer;
+
+ /**
+ * The character buffer. Characters are stored here on their way into
+ * the encoder.
+ */
+ private CharBuffer charBuffer;
+
+ private void writeBuffer() throws IOException
+ {
+ byteBuffer.flip();
+ byteChannel.write(byteBuffer);
+ }
+
+ /**
+ * Create a new instance, given the output byte channel, the encoder
+ * to use, and the minimum buffer capacity.
+ */
+ public ChannelWriter(WritableByteChannel ch, CharsetEncoder enc,
+ int minBufferCap)
+ {
+ this.byteChannel = ch;
+ this.enc = enc;
+ if (minBufferCap == -1)
+ minBufferCap = DEFAULT_BUFFER_CAP;
+ this.byteBuffer
+ = ByteBuffer.allocate((int) (minBufferCap * enc.maxBytesPerChar()));
+ this.charBuffer = CharBuffer.allocate(minBufferCap);
+ this.charBuffer.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.Writer#flush()
+ */
+ public void flush() throws IOException
+ {
+ // Presumably if we have characters in our buffer, it is
+ // due to an underflow. So we don't bother trying to flush
+ // that here.
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.Writer#close()
+ */
+ public void close() throws IOException
+ {
+ synchronized (lock)
+ {
+ if (enc == null)
+ throw new IOException("writer already closed");
+
+ byteBuffer.clear();
+ charBuffer.flip();
+ CoderResult res = enc.encode(charBuffer, byteBuffer, true);
+ if (res.isError() || res.isMalformed() || res.isUnmappable())
+ res.throwException();
+ writeBuffer();
+
+ byteBuffer.clear();
+ res = enc.flush(byteBuffer);
+ if (res.isError() || res.isMalformed() || res.isUnmappable())
+ res.throwException();
+ writeBuffer();
+ enc = null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.Writer#write(char[], int, int)
+ */
+ public void write(char[] buf, int offset, int len) throws IOException
+ {
+ synchronized (lock)
+ {
+ if (enc == null)
+ throw new IOException("writer already closed");
+ int lastLen = -1;
+ while (len > 0)
+ {
+ // Copy data into our character buffer.
+ int allowed = Math.min(charBuffer.remaining(), len);
+ charBuffer.put(buf, offset, allowed);
+ // Update for the next pass through the loop.
+ offset += allowed;
+ len -= allowed;
+ charBuffer.flip();
+ // If we didn't make any progress, we want to clean up
+ // and save our state for the next write().
+ if (len == lastLen)
+ {
+ if (len <= charBuffer.remaining())
+ {
+ charBuffer.put(buf, offset, len);
+ charBuffer.flip();
+ }
+ else
+ {
+ CharBuffer ncb = CharBuffer.allocate(charBuffer.length()
+ + len);
+ ncb.put(charBuffer);
+ ncb.put(buf, offset, len);
+ charBuffer = ncb;
+ }
+ break;
+ }
+ lastLen = len;
+
+ // Convert.
+ byteBuffer.clear();
+ CoderResult res = enc.encode(charBuffer, byteBuffer, false);
+ // Compact here, as we want to leave the buffer in the
+ // right state for any future put()s.
+ charBuffer.compact();
+ if (res.isError() || res.isMalformed() || res.isUnmappable())
+ res.throwException();
+ // Write the byte buffer to the output channel.
+ writeBuffer();
+ }
+ }
+ }
+}