1 /*
   2  * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.awt.*;
  28 
  29 /**
  30  * A class to perform rendering of the glyphs.
  31  * This can be implemented to be stateless, or
  32  * to hold some information as a cache to
  33  * facilitate faster rendering and model/view
  34  * translation.  At a minimum, the GlyphPainter
  35  * allows a View implementation to perform its
  36  * duties independent of a particular version
  37  * of JVM and selection of capabilities (i.e.
  38  * shaping for i18n, etc).
  39  * <p>
  40  * This implementation is intended for operation
  41  * under the JDK1.1 API of the Java Platform.
  42  * Since the JDK is backward compatible with
  43  * JDK1.1 API, this class will also function on
  44  * Java 2.  The JDK introduces improved
  45  * API for rendering text however, so the GlyphPainter2
  46  * is recommended for the DK.
  47  *
  48  * @author  Timothy Prinzing
  49  * @see GlyphView
  50  */
  51 class GlyphPainter1 extends GlyphView.GlyphPainter {
  52 
  53     /**
  54      * Determine the span the glyphs given a start location
  55      * (for tab expansion).
  56      */
  57     public float getSpan(GlyphView v, int p0, int p1,
  58                          TabExpander e, float x) {
  59         sync(v);
  60         Segment text = v.getText(p0, p1);
  61         int[] justificationData = getJustificationData(v);       
  62                              
  63         float width = Utilities.getTabbedTextWidth(v, text, metrics, x, e, p0,
  64                                                  justificationData);
  65         SegmentCache.releaseSharedSegment(text);
  66         return width;
  67     }
  68 
  69     public float getHeight(GlyphView v) {
  70         sync(v);
  71         return metrics.getHeight();
  72     }
  73 
  74     /**
  75      * Fetches the ascent above the baseline for the glyphs
  76      * corresponding to the given range in the model.
  77      */
  78     public float getAscent(GlyphView v) {
  79         sync(v);
  80         return metrics.getAscent();
  81     }
  82 
  83     /**
  84      * Fetches the descent below the baseline for the glyphs
  85      * corresponding to the given range in the model.
  86      */
  87     public float getDescent(GlyphView v) {
  88         sync(v);
  89         return metrics.getDescent();
  90     }
  91 
  92     /**
  93      * Paints the glyphs representing the given range.
  94      */
  95     public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) {
  96         sync(v);
  97         Segment text;
  98         TabExpander expander = v.getTabExpander();
  99         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 100 
 101         // determine the x coordinate to render the glyphs
 102         float x = alloc.x;
 103         int p = v.getStartOffset();
 104         int[] justificationData = getJustificationData(v);
 105         if (p != p0) {
 106             text = v.getText(p, p0);
 107             float width = Utilities.getTabbedTextWidth(v, text, metrics, x,
 108                                                        expander, p,
 109                                                        justificationData);
 110             x += width;
 111             SegmentCache.releaseSharedSegment(text);
 112         }
 113 
 114         // determine the y coordinate to render the glyphs
 115         float y = alloc.y + metrics.getHeight() - metrics.getDescent();
 116 
 117         // render the glyphs
 118         text = v.getText(p0, p1);
 119         g.setFont(metrics.getFont());
 120 
 121         Utilities.drawTabbedText(v, text, x, y, g, expander,p0,
 122                                  justificationData, true);
 123         SegmentCache.releaseSharedSegment(text);
 124     }
 125 
 126     public Shape modelToView(GlyphView v, int pos, Position.Bias bias,
 127                              Shape a) throws BadLocationException {
 128 
 129         sync(v);
 130         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 131         int p0 = v.getStartOffset();
 132         int p1 = v.getEndOffset();
 133         TabExpander expander = v.getTabExpander();
 134         Segment text;
 135 
 136         if(pos == p1) {
 137             // The caller of this is left to right and borders a right to
 138             // left view, return our end location.
 139             return new Rectangle(alloc.x + alloc.width, alloc.y, 0,
 140                                  metrics.getHeight());
 141         }
 142         if ((pos >= p0) && (pos <= p1)) {
 143             // determine range to the left of the position
 144             text = v.getText(p0, pos);
 145             int[] justificationData = getJustificationData(v);
 146             int width = Utilities.getTabbedTextWidth(v, text, metrics, alloc.x, expander, p0,
 147                                                      justificationData);
 148             SegmentCache.releaseSharedSegment(text);
 149             return new Rectangle(alloc.x + width, alloc.y, 0, metrics.getHeight());
 150         }
 151         throw new BadLocationException("modelToView - can't convert", p1);
 152     }
 153 
 154     /**
 155      * Provides a mapping from the view coordinate space to the logical
 156      * coordinate space of the model.
 157      *
 158      * @param v the view containing the view coordinates
 159      * @param x the X coordinate
 160      * @param y the Y coordinate
 161      * @param a the allocated region to render into
 162      * @param biasReturn always returns <code>Position.Bias.Forward</code>
 163      *   as the zero-th element of this array
 164      * @return the location within the model that best represents the
 165      *  given point in the view
 166      * @see View#viewToModel
 167      */
 168     public int viewToModel(GlyphView v, float x, float y, Shape a,
 169                            Position.Bias[] biasReturn) {
 170 
 171         sync(v);
 172         Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
 173         int p0 = v.getStartOffset();
 174         int p1 = v.getEndOffset();
 175         TabExpander expander = v.getTabExpander();
 176         Segment text = v.getText(p0, p1);
 177         int[] justificationData = getJustificationData(v);
 178         int offs = Utilities.getTabbedTextOffset(v, text, metrics,
 179                                                  alloc.x, (int) x, expander, p0,
 180                                                  justificationData);
 181         SegmentCache.releaseSharedSegment(text);
 182         int retValue = p0 + offs;
 183         if(retValue == p1) {
 184             // No need to return backward bias as GlyphPainter1 is used for
 185             // ltr text only.
 186             retValue--;
 187         }
 188         biasReturn[0] = Position.Bias.Forward;
 189         return retValue;
 190     }
 191 
 192     /**
 193      * Determines the best location (in the model) to break
 194      * the given view.
 195      * This method attempts to break on a whitespace
 196      * location.  If a whitespace location can't be found, the
 197      * nearest character location is returned.
 198      *
 199      * @param v the view
 200      * @param p0 the location in the model where the
 201      *  fragment should start its representation >= 0
 202      * @param x the graphic location along the axis that the
 203      *  broken view would occupy >= 0; this may be useful for
 204      *  things like tab calculations
 205      * @param len specifies the distance into the view
 206      *  where a potential break is desired >= 0
 207      * @return the model location desired for a break
 208      * @see View#breakView
 209      */
 210     public int getBoundedPosition(GlyphView v, int p0, float x, float len) {
 211         sync(v);
 212         TabExpander expander = v.getTabExpander();
 213         Segment s = v.getText(p0, v.getEndOffset());
 214         int[] justificationData = getJustificationData(v);
 215         int index = Utilities.getTabbedTextOffset(v, s, metrics, x, (x+len),
 216                                                   expander, p0, false,
 217                                                   justificationData, true);
 218         SegmentCache.releaseSharedSegment(s);
 219         int p1 = p0 + index;
 220         return p1;
 221     }
 222 
 223     @SuppressWarnings("deprecation")
 224     void sync(GlyphView v) {
 225         Font f = v.getFont();
 226         FontMetrics fm = null;
 227         Container c = v.getContainer();
 228         if (c != null) {
 229             fm = c.getFontMetrics(f);
 230         }
 231         if ((metrics == null) || (! f.equals(metrics.getFont()))
 232                 || (! metrics.equals(fm))) {
 233             // fetch a new FontMetrics
 234             metrics = (c != null) ? fm :
 235                 Toolkit.getDefaultToolkit().getFontMetrics(f);
 236         }
 237     }
 238 
 239 
 240 
 241     /**
 242      * @return justificationData from the ParagraphRow this GlyphView
 243      * is in or {@code null} if no justification is needed
 244      */
 245     private int[] getJustificationData(GlyphView v) {
 246         View parent = v.getParent();
 247         int [] ret = null;
 248         if (parent instanceof ParagraphView.Row) {
 249             ParagraphView.Row row = ((ParagraphView.Row) parent);
 250             ret = row.justificationData;
 251         }
 252         return ret;
 253     }
 254 
 255     // --- variables ---------------------------------------------
 256 
 257     FontMetrics metrics;
 258 }