summaryrefslogtreecommitdiffstats
path: root/awt/org/apache/harmony/awt/gl/font/TextDecorator.java
diff options
context:
space:
mode:
Diffstat (limited to 'awt/org/apache/harmony/awt/gl/font/TextDecorator.java')
-rw-r--r--awt/org/apache/harmony/awt/gl/font/TextDecorator.java433
1 files changed, 433 insertions, 0 deletions
diff --git a/awt/org/apache/harmony/awt/gl/font/TextDecorator.java b/awt/org/apache/harmony/awt/gl/font/TextDecorator.java
new file mode 100644
index 0000000..81905fd
--- /dev/null
+++ b/awt/org/apache/harmony/awt/gl/font/TextDecorator.java
@@ -0,0 +1,433 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * @author Oleg V. Khaschansky
+ * @version $Revision$
+ */
+
+package org.apache.harmony.awt.gl.font;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.font.TextAttribute;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedCharacterIterator.Attribute;
+import java.util.Map;
+
+/**
+ * This class is responsible for rendering text decorations like
+ * underline, strikethrough, text with background, etc.
+ */
+public class TextDecorator {
+ private static final TextDecorator inst = new TextDecorator();
+ private TextDecorator() {}
+ static TextDecorator getInstance() {
+ return inst;
+ }
+
+ /**
+ * This class encapsulates a set of decoration attributes for a single text run.
+ */
+ static class Decoration {
+ private static final BasicStroke UNDERLINE_LOW_ONE_PIXEL_STROKE =
+ new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10);
+
+ private static final BasicStroke UNDERLINE_LOW_TWO_PIXEL_STROKE =
+ new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10);
+
+ private static final BasicStroke UNDERLINE_LOW_DOTTED_STROKE =
+ new BasicStroke(
+ 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
+ new float[] { 1, 1 }, 0
+ );
+
+ private static final BasicStroke UNDERLINE_LOW_DOTTED_STROKE2 =
+ new BasicStroke(
+ 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
+ new float[] { 1, 1 }, 1
+ );
+
+ private static final BasicStroke UNDERLINE_LOW_DASHED_STROKE =
+ new BasicStroke(
+ 1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
+ new float[] { 4, 4 }, 0
+ );
+
+ boolean ulOn = false; // Have standard underline?
+ BasicStroke ulStroke;
+
+ BasicStroke imUlStroke; // Stroke for INPUT_METHOD_UNDERLINE
+ BasicStroke imUlStroke2; // Specially for UNDERLINE_LOW_GRAY
+
+ boolean strikeThrough;
+ BasicStroke strikeThroughStroke;
+
+ boolean haveStrokes = false; // Strokes already created?
+
+ boolean swapBfFg;
+ Paint bg; // background color
+ Paint fg; // foreground color
+
+ Paint graphicsPaint; // Slot for saving current paint
+
+ Decoration(
+ Integer imUl,
+ boolean swap,
+ boolean sth,
+ Paint bg, Paint fg,
+ boolean ulOn) {
+
+ if (imUl != null) {
+ // Determine which stroke to use
+ if (imUl == TextAttribute.UNDERLINE_LOW_ONE_PIXEL) {
+ this.imUlStroke = Decoration.UNDERLINE_LOW_ONE_PIXEL_STROKE;
+ } else if (imUl == TextAttribute.UNDERLINE_LOW_TWO_PIXEL) {
+ this.imUlStroke = Decoration.UNDERLINE_LOW_TWO_PIXEL_STROKE;
+ } else if (imUl == TextAttribute.UNDERLINE_LOW_DOTTED) {
+ this.imUlStroke = Decoration.UNDERLINE_LOW_DOTTED_STROKE;
+ } else if (imUl == TextAttribute.UNDERLINE_LOW_GRAY) {
+ this.imUlStroke = Decoration.UNDERLINE_LOW_DOTTED_STROKE;
+ this.imUlStroke2 = Decoration.UNDERLINE_LOW_DOTTED_STROKE2;
+ } else if (imUl == TextAttribute.UNDERLINE_LOW_DASHED) {
+ this.imUlStroke = Decoration.UNDERLINE_LOW_DASHED_STROKE;
+ }
+ }
+
+ this.ulOn = ulOn; // Has underline
+ this.swapBfFg = swap;
+ this.strikeThrough = sth;
+ this.bg = bg;
+ this.fg = fg;
+ }
+
+ /**
+ * Creates strokes of proper width according to the info
+ * stored in the BasicMetrics
+ * @param metrics - basic metrics
+ */
+ private void getStrokes(BasicMetrics metrics) {
+ if (!haveStrokes) {
+ if (strikeThrough) {
+ strikeThroughStroke =
+ new BasicStroke(
+ metrics.strikethroughThickness,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 10
+ );
+ }
+
+ if (ulOn) {
+ ulStroke =
+ new BasicStroke(
+ metrics.underlineThickness,
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER,
+ 10
+ );
+ }
+
+ haveStrokes = true;
+ }
+ }
+ }
+
+ /**
+ * Creates Decoration object from the set of text attributes
+ * @param attributes - text attributes
+ * @return Decoration object
+ */
+ static Decoration getDecoration(Map<? extends Attribute, ?> attributes) {
+ if (attributes == null) {
+ return null; // It is for plain text
+ }
+
+ Object underline = attributes.get(TextAttribute.UNDERLINE);
+ boolean hasStandardUnderline = underline == TextAttribute.UNDERLINE_ON;
+
+ Object imUnderline = attributes.get(TextAttribute.INPUT_METHOD_UNDERLINE);
+ Integer imUl = (Integer) imUnderline;
+
+ boolean swapBgFg =
+ TextAttribute.SWAP_COLORS_ON.equals(
+ attributes.get(TextAttribute.SWAP_COLORS)
+ );
+
+ boolean strikeThrough =
+ TextAttribute.STRIKETHROUGH_ON.equals(
+ attributes.get(TextAttribute.STRIKETHROUGH)
+ );
+
+ Paint fg = (Paint) attributes.get(TextAttribute.FOREGROUND);
+ Paint bg = (Paint) attributes.get(TextAttribute.BACKGROUND);
+
+ if (
+ !hasStandardUnderline &&
+ imUnderline == null &&
+ fg == null &&
+ bg == null &&
+ !swapBgFg &&
+ !strikeThrough
+ ) {
+ return null;
+ }
+ return new Decoration(imUl, swapBgFg, strikeThrough, bg, fg, hasStandardUnderline);
+ }
+
+ /**
+ * Fills the background before drawing if needed.
+ *
+ * @param trs - text segment
+ * @param g2d - graphics to draw to
+ * @param xOffset - offset in X direction to the upper left corner of the
+ * layout from the origin of the graphics
+ * @param yOffset - offset in Y direction to the upper left corner of the
+ * layout from the origin of the graphics
+ */
+ static void prepareGraphics(
+ TextRunSegment trs, Graphics2D g2d,
+ float xOffset, float yOffset
+ ) {
+ Decoration d = trs.decoration;
+
+ if (d.fg == null && d.bg == null && d.swapBfFg == false) {
+ return; // Nothing to do
+ }
+
+ d.graphicsPaint = g2d.getPaint();
+
+ if (d.fg == null) {
+ d.fg = d.graphicsPaint;
+ }
+
+ if (d.swapBfFg) {
+ // Fill background area
+ g2d.setPaint(d.fg);
+ Rectangle2D bgArea = trs.getLogicalBounds();
+ Rectangle2D toFill =
+ new Rectangle2D.Double(
+ bgArea.getX() + xOffset,
+ bgArea.getY() + yOffset,
+ bgArea.getWidth(),
+ bgArea.getHeight()
+ );
+ g2d.fill(toFill);
+
+ // Set foreground color
+ g2d.setPaint(d.bg == null ? Color.WHITE : d.bg);
+ } else {
+ if (d.bg != null) { // Fill background area
+ g2d.setPaint(d.bg);
+ Rectangle2D bgArea = trs.getLogicalBounds();
+ Rectangle2D toFill =
+ new Rectangle2D.Double(
+ bgArea.getX() + xOffset,
+ bgArea.getY() + yOffset,
+ bgArea.getWidth(),
+ bgArea.getHeight()
+ );
+ g2d.fill(toFill);
+ }
+
+ // Set foreground color
+ g2d.setPaint(d.fg);
+ }
+ }
+
+ /**
+ * Restores the original state of the graphics if needed
+ * @param d - decoration
+ * @param g2d - graphics
+ */
+ static void restoreGraphics(Decoration d, Graphics2D g2d) {
+ if (d.fg == null && d.bg == null && d.swapBfFg == false) {
+ return; // Nothing to do
+ }
+
+ g2d.setPaint(d.graphicsPaint);
+ }
+
+ /**
+ * Renders the text decorations
+ * @param trs - text run segment
+ * @param g2d - graphics to render to
+ * @param xOffset - offset in X direction to the upper left corner
+ * of the layout from the origin of the graphics
+ * @param yOffset - offset in Y direction to the upper left corner
+ * of the layout from the origin of the graphics
+ */
+ static void drawTextDecorations(
+ TextRunSegment trs, Graphics2D g2d,
+ float xOffset, float yOffset
+ ) {
+ Decoration d = trs.decoration;
+
+ if (!d.ulOn && d.imUlStroke == null && !d.strikeThrough) {
+ return; // Nothing to do
+ }
+
+ float left = xOffset + (float) trs.getLogicalBounds().getMinX();
+ float right = xOffset + (float) trs.getLogicalBounds().getMaxX();
+
+ Stroke savedStroke = g2d.getStroke();
+
+ d.getStrokes(trs.metrics);
+
+ if (d.strikeThrough) {
+ float y = trs.y + yOffset + trs.metrics.strikethroughOffset;
+ g2d.setStroke(d.strikeThroughStroke);
+ g2d.draw(new Line2D.Float(left, y, right, y));
+ }
+
+ if (d.ulOn) {
+ float y = trs.y + yOffset + trs.metrics.underlineOffset;
+ g2d.setStroke(d.ulStroke);
+ g2d.draw(new Line2D.Float(left, y, right, y));
+ }
+
+ if (d.imUlStroke != null) {
+ float y = trs.y + yOffset + trs.metrics.underlineOffset;
+ g2d.setStroke(d.imUlStroke);
+ g2d.draw(new Line2D.Float(left, y, right, y));
+ if (d.imUlStroke2 != null) {
+ y++;
+ g2d.setStroke(d.imUlStroke2);
+ g2d.draw(new Line2D.Float(left, y, right, y));
+ }
+ }
+
+ g2d.setStroke(savedStroke);
+ }
+
+ /**
+ * Extends the visual bounds of the text run segment to
+ * include text decorations.
+ * @param trs - text segment
+ * @param segmentBounds - bounds of the undecorated text
+ * @param d - decoration
+ * @return extended bounds
+ */
+ static Rectangle2D extendVisualBounds(
+ TextRunSegment trs,
+ Rectangle2D segmentBounds,
+ Decoration d
+ ) {
+ if (d == null) {
+ return segmentBounds;
+ }
+ double minx = segmentBounds.getMinX();
+ double miny = segmentBounds.getMinY();
+ double maxx = segmentBounds.getMaxX();
+ double maxy = segmentBounds.getMaxY();
+
+ Rectangle2D lb = trs.getLogicalBounds();
+
+ if (d.swapBfFg || d.bg != null) {
+ minx = Math.min(lb.getMinX() - trs.x, minx);
+ miny = Math.min(lb.getMinY() - trs.y, miny);
+ maxx = Math.max(lb.getMaxX() - trs.x, maxx);
+ maxy = Math.max(lb.getMaxY() - trs.y, maxy);
+ }
+
+ if (d.ulOn || d.imUlStroke != null || d.strikeThrough) {
+ minx = Math.min(lb.getMinX() - trs.x, minx);
+ maxx = Math.max(lb.getMaxX() - trs.x, maxx);
+
+ d.getStrokes(trs.metrics);
+
+ if (d.ulStroke != null) {
+ maxy = Math.max(
+ maxy,
+ trs.metrics.underlineOffset +
+ d.ulStroke.getLineWidth()
+ );
+ }
+
+ if (d.imUlStroke != null) {
+ maxy = Math.max(
+ maxy,
+ trs.metrics.underlineOffset +
+ d.imUlStroke.getLineWidth() +
+ (d.imUlStroke2 == null ? 0 : d.imUlStroke2.getLineWidth())
+ );
+ }
+ }
+
+ return new Rectangle2D.Double(minx, miny, maxx-minx, maxy-miny);
+ }
+
+ /**
+ * Extends the outline of the text run segment to
+ * include text decorations.
+ * @param trs - text segment
+ * @param segmentOutline - outline of the undecorated text
+ * @param d - decoration
+ * @return extended outline
+ */
+ static Shape extendOutline(
+ TextRunSegment trs,
+ Shape segmentOutline,
+ Decoration d
+ ) {
+ if (d == null || !d.ulOn && d.imUlStroke == null && !d.strikeThrough) {
+ return segmentOutline; // Nothing to do
+ }
+
+ Area res = new Area(segmentOutline);
+
+ float left = (float) trs.getLogicalBounds().getMinX() - trs.x;
+ float right = (float) trs.getLogicalBounds().getMaxX() - trs.x;
+
+ d.getStrokes(trs.metrics);
+
+ if (d.strikeThrough) {
+ float y = trs.metrics.strikethroughOffset;
+ res.add(new Area(d.strikeThroughStroke.createStrokedShape(
+ new Line2D.Float(left, y, right, y)
+ )));
+ }
+
+ if (d.ulOn) {
+ float y = trs.metrics.underlineOffset;
+ res.add(new Area(d.ulStroke.createStrokedShape(
+ new Line2D.Float(left, y, right, y)
+ )));
+ }
+
+ if (d.imUlStroke != null) {
+ float y = trs.metrics.underlineOffset;
+ res.add(new Area(d.imUlStroke.createStrokedShape(
+ new Line2D.Float(left, y, right, y)
+ )));
+
+ if (d.imUlStroke2 != null) {
+ y++;
+ res.add(new Area(d.imUlStroke2.createStrokedShape(
+ new Line2D.Float(left, y, right, y)
+ )));
+ }
+ }
+
+ return res;
+ }
+}