/* * 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; } }