Package com.ibm.richtext.textformat

Source Code of com.ibm.richtext.textformat.BidiParagraphRenderer

/*
* (C) Copyright IBM Corp. 1998-2007.  All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
// Requires Java2
package com.ibm.richtext.textformat;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;

import java.util.Vector;

import com.ibm.richtext.styledtext.MConstText;
import com.ibm.richtext.styledtext.MTabRuler;
import com.ibm.richtext.styledtext.TabStop;

import com.ibm.richtext.textlayout.attributes.AttributeMap;
import com.ibm.richtext.textlayout.attributes.TextAttribute;

import com.ibm.richtext.textlayout.Graphics2DConversion;

///*JDK12IMPORTS
import java.awt.Graphics2D;

import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextHitInfo;

import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
//JDK12IMPORTS*/
/*JDK11IMPORTS
import com.ibm.richtext.textlayout.Graphics2D;

import com.ibm.richtext.textlayout.FontRenderContext;
import com.ibm.richtext.textlayout.TextLayout;
import com.ibm.richtext.textlayout.LineBreakMeasurer;
import com.ibm.richtext.textlayout.TextHitInfo;

import com.ibm.richtext.textlayout.AffineTransform;
import com.ibm.richtext.textlayout.GeneralPath;
import com.ibm.richtext.textlayout.Rectangle2D;
JDK11IMPORTS*/

final class BidiParagraphRenderer extends ParagraphRenderer {

    static final String COPYRIGHT =
                "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
    private final class BidiSegment {
        TextLayout fLayout;
        Rectangle2D.Float fBounds;
        int fDistanceFromLeadingMargin;
    }

    private final class BidiLayoutInfo extends LayoutInfo
    {                   
        int fCharLength;      // number of characters on line (was fLength)
        int fAscent;
        int fDescent;
        int fLeading;
        int fVisibleAdvance;  // distance along line direction ie width
        int fTotalAdvance;    // distance along line direction including trailing whitespace

        int fLeadingMargin;   // screen distance from leading margin

        boolean fLeftToRight; // true iff the orientation is left-to-right

        final Vector fSegments = new Vector(); // segments to render, in logical order

        public int getCharLength() {
            return fCharLength;
        }

        public int getAscent() {
            return fAscent;
        }

        public int getDescent() {
            return fDescent;
        }

        public int getLeading() {
            return fLeading;
        }

        public int getVisibleAdvance() {
            return fVisibleAdvance;
        }

        public int getTotalAdvance() {
            return fTotalAdvance;
        }

        public int getLeadingMargin() {
            return fLeadingMargin;
        }

        public boolean isLeftToRight() {
            return fLeftToRight;
        }

        public int getHeight() {
            return fAscent + fDescent + fLeading;
        }

        public String toString()
        {
            return "LayoutInfo(charStart: " + getCharStart(0) +
                ", fCharLength: " + fCharLength +
                ", fAscent: " + fAscent +
                ", fDescent: " + fDescent +
                ", fVisibleAdvance: " + fVisibleAdvance +
                ", fTotalAdvance: " + fTotalAdvance +
                ", fLeadingMargin: " + fLeadingMargin +
                ")";
        }

        BidiParagraphRenderer fRenderer;

        // just delegate to renderer for now

        public void renderWithHighlight(int lengthBasis,
                                        Graphics2D g,
                                        int lineBound,
                                        int x,
                                        int y,
                                        TextOffset selStart,
                                        TextOffset selStop,
                                        Color highlightColor) {

            fRenderer.renderWithHighlight(this,
                                          lengthBasis,
                                          g,
                                          lineBound,
                                          x,
                                          y,
                                          selStart,
                                          selStop,
                                          highlightColor);
        }

        public void render(int lengthBasis,
                           Graphics2D g,
                           int lineBound,
                           int x,
                           int y) {
            fRenderer.render(this, lengthBasis, g, lineBound, x, y);
        }

        public void renderCaret(MConstText text,
                                int lengthBasis,
                                Graphics2D g,
                                int lineBound,
                                int x,
                                int y,
                                int charOffset,
                                Color strongCaretColor,
                                Color weakCaretColor) {
            fRenderer.renderCaret(this, text, lengthBasis, g, lineBound, x, y, charOffset,
                                        strongCaretColor, weakCaretColor);
        }

        public TextOffset pixelToOffset(int lengthBasis,
                                        TextOffset result,
                                        int lineBound,
                                        int x,
                                        int y) {
            return fRenderer.pixelToOffset(this, lengthBasis, result, lineBound, x, y);
        }

        public Rectangle caretBounds(MConstText text,
                                     int lengthBasis,
                                     int lineBound,
                                     int charOffset,
                                     int x,
                                     int y) {
            return fRenderer.caretBounds(this, text, lengthBasis, lineBound, charOffset, x, y);
        }
       
        public int strongCaretBaselinePosition(int lengthBasis,
                                               int lineBound,
                                               int charOffset) {

            return fRenderer.strongCaretBaselinePosition(this, lengthBasis, lineBound, charOffset);
        }

        public int getNextOffset(int lengthBasis,
                                 int charOffset,
                                 short dir) {

            return fRenderer.getNextOffset(this, lengthBasis, charOffset, dir);
        }
    }

    //private static final int FLUSH_LEADING = TextAttribute.FLUSH_LEADING.intValue();
    private static final int FLUSH_CENTER = TextAttribute.FLUSH_CENTER.intValue();
    private static final int FLUSH_TRAILING = TextAttribute.FLUSH_TRAILING.intValue();
    private static final int FULLY_JUSTIFIED = TextAttribute.FULLY_JUSTIFIED.intValue();

    private AttributeMap cacheStyle = null;

    private float fLeadingMargin;
    private float fTrailingMargin;
    private float fFirstLineIndent;
    private float fMinLineSpacing;
    private float fExtraLineSpacing;
   
    private int fFlush = -1;
    private MTabRuler fTabRuler;
   
    private boolean fLtrDefault;
    private DefaultCharacterMetric fDefaultCharMetric;
   
    BidiParagraphRenderer(AttributeMap pStyle, DefaultCharacterMetric defaultCharMetric) {

        fDefaultCharMetric = defaultCharMetric;
        initRenderer(pStyle);
    }

    private float getFloatValue(Object key, AttributeMap style) {
        return ((Float)style.get(key)).floatValue();
    }
   
    private int getIntValue(Object key, AttributeMap style) {
        return ((Integer)style.get(key)).intValue();
    }
   
    /**
     * NOTE:  it is illegal to initialize a StandardParagraphRenderer for any style
     * other than the one it was created with.
     */
    public void initRenderer(AttributeMap pStyle) {

        if (cacheStyle == null) {

            fLeadingMargin = getFloatValue(TextAttribute.LEADING_MARGIN, pStyle);
            fTrailingMargin = getFloatValue(TextAttribute.TRAILING_MARGIN, pStyle);
            fFirstLineIndent = getFloatValue(TextAttribute.FIRST_LINE_INDENT, pStyle);
            fMinLineSpacing = getFloatValue(TextAttribute.MIN_LINE_SPACING, pStyle);
            fExtraLineSpacing = getFloatValue(TextAttribute.EXTRA_LINE_SPACING, pStyle);

            fFlush = getIntValue(TextAttribute.LINE_FLUSH, pStyle);

            fTabRuler = (MTabRuler) pStyle.get(TextAttribute.TAB_RULER);
           
            Object runDir = pStyle.get(TextAttribute.RUN_DIRECTION);
            fLtrDefault = !TextAttribute.RUN_DIRECTION_RTL.equals(runDir);

            cacheStyle = pStyle;
        }
        else if (pStyle != cacheStyle) {
            if (!pStyle.equals(cacheStyle)) {
                throw new Error("Attempt to share BidiParagraphRenderer between styles!");
            }
            else {
                cacheStyle = pStyle;
            }
        }
    }

    private static boolean isTab(char ch) {
        return ch == '\t';
    }

    /**
     * Fill in info with the next line.
     * @param measurer the LineBreakMeasurer for this paragraph.
     *  Current position should be the first character on the line.
     *  If null, a 0-length line is generated.  If measurer is null
     *  then paragraphStart and paragraphLimit should be equal.
     */
    // Usually totalFormatWidth and lineBound will be the same.
    // totalFormatWidth is used for wrapping, but lineBound is
    // for flushing.  These may be different for unwrapped text,
    // for example.
    public LayoutInfo layout(MConstText text,
                             LayoutInfo layoutToReuse,
                             LineBreakMeasurer measurer,
                             FontRenderContext frc,
                             int paragraphStart,
                             int paragraphLimit,
                             int totalFormatWidth,
                             int lineBound) {

        if ((measurer==null) != (paragraphStart==paragraphLimit)) {
            throw new IllegalArgumentException(
                    "measurer, paragraphStart, paragraphLimit are wrong.");
        }
        BidiLayoutInfo line = null;

        try {
            line = (BidiLayoutInfo) layoutToReuse;
        }
        catch(ClassCastException e) {
        }

        if (line == null) {
            line = new BidiLayoutInfo();
        }

        line.fRenderer = this;

        final int lineCharStart = measurer==null? paragraphStart : measurer.getPosition();
        line.setCharStart(lineCharStart);

        final int lineIndent = (lineCharStart==paragraphStart)? (int) fFirstLineIndent : 0;

        int formatWidth = totalFormatWidth - (int) (fLeadingMargin + fTrailingMargin);
        computeLineMetrics(text, line, measurer, frc,
                            paragraphStart, paragraphLimit, formatWidth, lineIndent);

        // position the line according to the line flush
        if (fFlush == FLUSH_TRAILING || fFlush == FLUSH_CENTER) {
            int lineArea = lineBound - (int) (fLeadingMargin + fTrailingMargin);
            int advanceDifference = lineArea - line.fVisibleAdvance;

            if (fFlush == FLUSH_TRAILING) {
                line.fLeadingMargin = ((int) (fLeadingMargin)) + advanceDifference;
            }
            else if (fFlush == FLUSH_CENTER) {
                line.fLeadingMargin = (int) (fLeadingMargin + advanceDifference/2);
            }
        }
        else {
            line.fLeadingMargin = (int) fLeadingMargin;
        }

        return line;
    }

    /**
     * Fill in the following fields in line:
     * fCharLength, fAscent, fDescent, fLeading, fVisibleAdvance,
     * fTotalAdvance.
     * Uses: line.fLeadingMargin
     * @param formatWidth the width to fit the line into.
     */
    private void computeLineMetrics(MConstText text,
                                    BidiLayoutInfo line,
                                    LineBreakMeasurer measurer,
                                    FontRenderContext frc,
                                    final int paragraphStart,
                                    final int paragraphLimit,
                                    final int formatWidth,
                                    final int lineIndent) {

        int segmentCount = 0;
        /* variable not used boolean firstLine = measurer==null ||
                            measurer.getPosition() == paragraphStart; */

        if (measurer != null) {
            computeSegments(text, line, measurer, paragraphLimit, formatWidth, lineIndent);

            // iterate through segments and accumulate ascent, descent,
            // leading, char length
            float ascent = 0;
            float descent = 0;
            float descentPlusLeading = 0;

            segmentCount = line.fSegments.size();
            for (int i=0; i < segmentCount; i++) {
                TextLayout layout = ((BidiSegment)line.fSegments.elementAt(i)).fLayout;
                ascent = Math.max(ascent, layout.getAscent());
                float segDescent = layout.getDescent();
                descent = Math.max(descent, segDescent);
                descentPlusLeading = Math.max(descentPlusLeading, segDescent+layout.getLeading());
                line.fCharLength += layout.getCharacterCount();
            }

            line.fAscent = (int) Math.ceil(ascent);
            line.fDescent = (int) Math.ceil(descent);
            line.fLeading = (int) Math.ceil(descentPlusLeading) - line.fDescent;
        }
        else {
            line.fLeftToRight = fLtrDefault;
            line.fSegments.removeAllElements();

            line.fCharLength = 0;

            AttributeMap style = text.characterStyleAt(paragraphStart);
            DefaultCharacterMetric.Metric cm = fDefaultCharMetric.getMetricForStyle(style);
            line.fAscent = cm.getAscent();
            line.fDescent = cm.getDescent();
            line.fLeading = cm.getLeading();

            line.fVisibleAdvance = line.fTotalAdvance = 0;
        }

        if (fExtraLineSpacing != 0) {
            line.fAscent += (int) Math.ceil(fExtraLineSpacing);
        }

        if (fMinLineSpacing != 0){
            int height = line.getHeight();
            if (height < fMinLineSpacing) {
                line.fAscent += Math.ceil(fMinLineSpacing - height);
            }
        }

        final int lineNaturalAdvance = line.fTotalAdvance;

        line.fTotalAdvance += lineIndent;
        line.fVisibleAdvance += lineIndent;

        if (measurer != null) {
            // Now fill in fBounds field of BidiSegments.  fBounds should tile
            // the line.
            final float lineHeight = line.getHeight();

            for (int i=1; i < segmentCount; i++) {

                BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(i-1);
                BidiSegment nextSegment = (BidiSegment) line.fSegments.elementAt(i);

                float origin;
                float width;

                if (line.fLeftToRight) {
                    origin = 0;
                    width = nextSegment.fDistanceFromLeadingMargin -
                                currentSegment.fDistanceFromLeadingMargin;
                }
                else {
                    origin = currentSegment.fDistanceFromLeadingMargin;
                    origin -= nextSegment.fDistanceFromLeadingMargin;
                    origin += (float) Math.ceil(nextSegment.fLayout.getAdvance());
                    width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;
                }
                currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);
            }

            // set last segment's bounds
            {
                BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(segmentCount-1);
                float origin;
                float width;

                if (line.fLeftToRight) {
                    origin = 0;
                    width = lineNaturalAdvance - currentSegment.fDistanceFromLeadingMargin;
                }
                else {
                    origin = currentSegment.fDistanceFromLeadingMargin - lineNaturalAdvance;
                    width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;
                }

                currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);
            }
        }
    }

    /**
     * Fill in fSegments, fLeftToRight.  measurer must not be null
     */
    private void computeSegments(MConstText text,
                                 BidiLayoutInfo line,
                                 LineBreakMeasurer measurer,
                                 final int paragraphLimit,
                                 final int formatWidth,
                                 final int lineIndent) {

        // Note on justification:  only the last segment of a line is
        // justified. 
        // Also, if a line ends in a tab it will not be justified.
        // This behavior is consistent with other word processors
        // I tried (MS Word and Lotus Word Pro).
       
        line.fSegments.removeAllElements();
        line.fCharLength = 0;

        TabStop currentTabStop = new TabStop((int)fLeadingMargin+lineIndent, TabStop.kLeading);

        int segmentLimit = measurer.getPosition();
        boolean firstSegment = true;

        int advanceFromLeadingMargin = lineIndent;

        boolean computeSegs = true;
       
        computeTabbedSegments: do {

            // compute sementLimit:
            if (segmentLimit <= measurer.getPosition()) {
                while (segmentLimit < paragraphLimit) {
                    if (isTab(text.at(segmentLimit++))) {
                        break;
                    }
                }
            }

            // NOTE:  adjust available width for center tab!!!
            //System.out.println("Format width: " + (formatWidth-advanceFromLeadingMargin) +
            //                   ";  segmentLimit: " + segmentLimit);

            int wrappingWidth = Math.max(formatWidth-advanceFromLeadingMargin, 0);
            TextLayout layout = null;
            if (firstSegment || wrappingWidth > 0 || segmentLimit > measurer.getPosition()+1) {
                layout = measurer.nextLayout(wrappingWidth, segmentLimit, !firstSegment);
            }

            if (layout == null) {
                if (firstSegment) {
                    // I doubt this would happen, but check anyway
                    throw new Error("First layout is null!");
                }
                break computeTabbedSegments;
            }
           
            final int measurerPos = measurer.getPosition();
            if (measurerPos < segmentLimit) {
                computeSegs = false;
                if (fFlush == FULLY_JUSTIFIED) {
                    layout = layout.getJustifiedLayout(wrappingWidth);
                }
            }
            else {
                computeSegs = !(measurerPos == paragraphLimit);
            }

            if (firstSegment) {
                firstSegment = false;
                // Have to get ltr off of layout.  Not available from measurer,
                // unfortunately.
                line.fLeftToRight = layout.isLeftToRight();
            }

            BidiSegment segment = new BidiSegment();
            segment.fLayout = layout;
            int layoutAdvance = (int) Math.ceil(layout.getAdvance());

            // position layout relative to leading margin, update logicalPositionOnLine
           
            int relativeTabPosition = currentTabStop.getPosition()-(int)fLeadingMargin;
            int logicalPositionOfLayout;
            switch (currentTabStop.getType()) {
                case TabStop.kTrailing:
                    logicalPositionOfLayout = Math.max(
                                    relativeTabPosition-layoutAdvance,
                                    advanceFromLeadingMargin);
                    break;
                case TabStop.kCenter:
                    logicalPositionOfLayout = Math.max(
                                    relativeTabPosition-(layoutAdvance/2),
                                    advanceFromLeadingMargin);
                    break;
                default// includes decimal tab right now
                    logicalPositionOfLayout = relativeTabPosition;
                    break;
            }

            // position layout in segment
            if (line.fLeftToRight) {
                segment.fDistanceFromLeadingMargin = logicalPositionOfLayout;
            }
            else {
                segment.fDistanceFromLeadingMargin = logicalPositionOfLayout+layoutAdvance;
            }

            // update advanceFromLeadingMargin
            advanceFromLeadingMargin = logicalPositionOfLayout + layoutAdvance;

            // add segment to segment Vector
            line.fSegments.addElement(segment);

            // get next tab
            currentTabStop = fTabRuler.nextTab((int)fLeadingMargin+advanceFromLeadingMargin);
            if (currentTabStop.getType() == TabStop.kLeading ||
                            currentTabStop.getType() == TabStop.kAuto)  {
                advanceFromLeadingMargin = currentTabStop.getPosition();
                //System.out.println("Advance from leading margin:" + advanceFromLeadingMargin);

            }
            else {
               //System.out.println("Non-leading tab, type=" + currentTabStop.getType());
            }

        } while (computeSegs);

        // Now compute fTotalAdvance, fVisibleAdvance.  These metrics may be affected
        // by a trailing tab.

        {
            BidiSegment lastSegment = (BidiSegment) line.fSegments.lastElement();
            TextLayout lastLayout = lastSegment.fLayout;

            if (line.fLeftToRight) {
                line.fTotalAdvance = (int) Math.ceil(lastLayout.getAdvance()) +
                                        lastSegment.fDistanceFromLeadingMargin;
                line.fVisibleAdvance = (int) Math.ceil(lastLayout.getVisibleAdvance()) +
                                        lastSegment.fDistanceFromLeadingMargin;
            }
            else {
                line.fTotalAdvance = lastSegment.fDistanceFromLeadingMargin;
                line.fVisibleAdvance = lastSegment.fDistanceFromLeadingMargin -
                                        (int) Math.ceil(lastLayout.getAdvance() -
                                            lastLayout.getVisibleAdvance());
            }

            if (isTab(text.at(measurer.getPosition()-1))) {
                line.fTotalAdvance = Math.max(line.fTotalAdvance,
                                                currentTabStop.getPosition());
            }
        }
    }

    /**
     * Return the highlight shape for the given character offsets.
     * The Shape returned is relative to the leftmost point on the
     * baseline of line.
     */
    private Shape getHighlightShape(BidiLayoutInfo line,
                                    int lengthBasis,
                                    int lineBound,
                                    int hlStart,
                                    int hlLimit) {

        if (hlStart >= hlLimit) {
            throw new IllegalArgumentException("Highlight range length is not positive.");
        }

        final int leadingMargin = (line.fLeftToRight)?
                line.fLeadingMargin : lineBound - line.fLeadingMargin;
        final int segmentCount = line.fSegments.size();

        Shape rval = null;
        GeneralPath highlightPath = null;

        int currentLayoutStart = line.getCharStart(lengthBasis);

        for (int i=0; i < segmentCount; i++) {

            BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);
            TextLayout layout = segment.fLayout;
            int charCount = layout.getCharacterCount();
            int currentLayoutLimit = currentLayoutStart + charCount;
            boolean rangesIntersect;
            if (hlStart <= currentLayoutStart) {
                rangesIntersect = hlLimit > currentLayoutStart;
            }
            else {
                rangesIntersect = hlStart < currentLayoutLimit;
            }

            if (rangesIntersect) {

                Shape currentHl = layout.getLogicalHighlightShape(
                                        Math.max(hlStart-currentLayoutStart, 0),
                                        Math.min(hlLimit-currentLayoutStart, charCount),
                                        segment.fBounds);

                float xTranslate;
                if (line.fLeftToRight) {
                    xTranslate = leadingMargin +
                                 segment.fDistanceFromLeadingMargin;
                }
                else {
                    xTranslate = leadingMargin -
                                 segment.fDistanceFromLeadingMargin;
                }

                if (xTranslate != 0) {
                    AffineTransform xform =
                        AffineTransform.getTranslateInstance(xTranslate, 0);
                    currentHl = xform.createTransformedShape(currentHl);
                }

                if (rval == null) {
                    rval = currentHl;
                }
                else {
                    if (highlightPath == null) {
                        highlightPath = new GeneralPath();
                        highlightPath.append(rval, false);
                        rval = highlightPath;
                    }
                    highlightPath.append(currentHl, false);
                }
            }
            currentLayoutStart = currentLayoutLimit;
        }

        return rval;
    }

    private void renderWithHighlight(BidiLayoutInfo line,
                                     int lengthBasis,
                                     Graphics2D g,
                                     int lineBound,
                                     int x,
                                     int y,
                                     TextOffset selStart,
                                     TextOffset selStop,
                                     Color highlightColor) {

        final int lineCharStart = line.getCharStart(lengthBasis);

        if (selStart != null && selStop != null && !selStart.equals(selStop) &&
                line.fCharLength != 0 &&
                selStart.fOffset < lineCharStart + line.fCharLength &&
                selStop.fOffset > lineCharStart) {

            Shape highlight = getHighlightShape(line, lengthBasis, lineBound, selStart.fOffset, selStop.fOffset);
            if (highlight != null) {
                Graphics2D hl = (Graphics2D) g.create();
                hl.setColor(highlightColor);
                hl.translate(x, y + line.fAscent);
                hl.fill(highlight);
            }
        }

        render(line, lengthBasis, g, lineBound, x, y);
    }

    /**
     * Draw the line into the graphics.  (x, y) is the upper-left corner
     * of the line.  The leading edge of a right-aligned line is aligned
     * to (x + lineBound).
     */
    private void render(BidiLayoutInfo line,
                        int lengthBasis,
                        Graphics2D g,
                        int lineBound,
                        int x,
                        int y) {

        final int leadingMargin = (line.fLeftToRight)?
                x + line.fLeadingMargin : x + lineBound - line.fLeadingMargin;
        final int baseline = y + line.fAscent;
        final int segmentCount = line.fSegments.size();

        for (int i=0; i < segmentCount; i++) {

            BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);

            float drawX;
            if (line.fLeftToRight) {
                drawX = leadingMargin + segment.fDistanceFromLeadingMargin;
            }
            else {
                drawX = leadingMargin - segment.fDistanceFromLeadingMargin;
            }

            segment.fLayout.draw(g, drawX, baseline);
        }
    }

    private TextOffset hitTestSegment(TextOffset result,
                                      int segmentCharStart,
                                      BidiSegment segment,
                                      int xInSegment,
                                      int yInSegment) {

        final TextLayout layout = segment.fLayout;
        final int charCount = layout.getCharacterCount();
        final int layoutAdvance = (int) Math.ceil(layout.getAdvance());
        Rectangle2D bounds = segment.fBounds;

        final boolean ltr = layout.isLeftToRight();

        if (ltr && (xInSegment >= layoutAdvance) || !ltr && (xInSegment <= 0)) {

            // pretend the extra space at the end of the line is a
            // tab and 'hit-test' it.
            double tabCenter;
            if (ltr) {
                tabCenter = (layoutAdvance+bounds.getMaxX()) / 2;
            }
            else {
                tabCenter = bounds.getX() / 2;
            }

            if ((xInSegment >= tabCenter) == ltr) {
                result.fOffset = charCount;
                result.fPlacement = TextOffset.BEFORE_OFFSET;
            }
            else {
                result.fOffset = charCount-1;
                result.fPlacement = TextOffset.AFTER_OFFSET;
            }
        }
        else {
            TextHitInfo info = layout.hitTestChar(xInSegment, yInSegment, segment.fBounds);
            result.fOffset = info.getInsertionIndex();
            if (result.fOffset == 0) {
                result.fPlacement = TextOffset.AFTER_OFFSET;
            }
            else if (result.fOffset == charCount) {
                result.fPlacement = TextOffset.BEFORE_OFFSET;
            }
            else {
                result.fPlacement = info.isLeadingEdge()?
                        TextOffset.AFTER_OFFSET : TextOffset.BEFORE_OFFSET;
            }
        }

        result.fOffset += segmentCharStart;
        return result;
    }

    /**
     * Return the offset at the point (x, y).  (x, y) is relative to the top-left
     * of the line.  The leading edge of a right-aligned line is aligned
     * to lineBound.
     */
    private TextOffset pixelToOffset(BidiLayoutInfo line,
                                     int lengthBasis,
                                     TextOffset result,
                                     int lineBound,
                                     int x,
                                     int y) {

        if (result == null) {
            result = new TextOffset();
        }

        final int yInSegment = y - line.fAscent;
        final int leadingMargin = (line.fLeftToRight)?
                line.fLeadingMargin : lineBound - line.fLeadingMargin;
        final int lineCharStart = line.getCharStart(lengthBasis);

        // first see if point is before leading edge of line
        final int segmentCount = line.fSegments.size();
        {
            int segLeadingMargin = leadingMargin;
            if (segmentCount > 0) {
                BidiSegment firstSeg = (BidiSegment) line.fSegments.elementAt(0);
                if (line.fLeftToRight) {
                    segLeadingMargin += firstSeg.fDistanceFromLeadingMargin;
                }
                else {
                    segLeadingMargin -= firstSeg.fDistanceFromLeadingMargin;
                    segLeadingMargin += (float) firstSeg.fBounds.getMaxX();
                }
            }
            if (line.fLeftToRight == (x <= segLeadingMargin)) {
                result.fOffset = lineCharStart;
                result.fPlacement = TextOffset.AFTER_OFFSET;
                return result;
            }
        }

        int segmentCharStart = lineCharStart;

        for (int i=0; i < segmentCount; i++) {

            BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);
            int segmentOrigin = line.fLeftToRight?
                            leadingMargin+segment.fDistanceFromLeadingMargin :
                            leadingMargin-segment.fDistanceFromLeadingMargin;
            int xInSegment = x - segmentOrigin;
            if (line.fLeftToRight) {
                if (segment.fBounds.getMaxX() > xInSegment) {
                    return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);
                }
            }
            else {
                if (segment.fBounds.getX() < xInSegment) {
                    return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);
                }
            }
            segmentCharStart += segment.fLayout.getCharacterCount();
        }

        result.fOffset = lineCharStart + line.fCharLength;
        result.fPlacement = TextOffset.BEFORE_OFFSET;
        return result;
    }

    private void renderCaret(BidiLayoutInfo line,
                             MConstText text,
                             int lengthBasis,
                             Graphics2D g,
                             int lineBound,
                             int x,
                             int y,
                             final int charOffset,
                             Color strongCaretColor,
                             Color weakCaretColor)
    {
        final int segmentCount = line.fSegments.size();
        final int lineStart = line.getCharStart(lengthBasis);

        int currentStart = lineStart;
        BidiSegment segment = null;
        int segmentIndex;

        for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
            segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
            int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
            if (currentEndpoint > charOffset) {
                break;
            }
            currentStart = currentEndpoint;
        }

        /*
            There are two choices here:
            1. get carets from a TextLayout and render them, or
            2. make up a caret ourselves and render it.
            We want to do 2 when:
                * there is no text on the line, or
                * the line ends with a tab and we are drawing the last caret on the line
            Otherwise, we want 1.
        */

        if (segmentIndex == segmentCount && segmentCount > 0) {
            // If we get here, line length is not 0, and charOffset is at end of line
            if (!isTab(text.at(charOffset-1))) {
                segmentIndex = segmentCount-1;
                segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
                currentStart = lineStart + line.getCharLength() -
                                    segment.fLayout.getCharacterCount();
            }
        }

        Object savedPaint = Graphics2DConversion.getColorState(g);

        try {
            if (segmentIndex < segmentCount) {
                TextLayout layout = segment.fLayout;
                int offsetInLayout = charOffset - currentStart;
                Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);
                g.setColor(strongCaretColor);
                int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
                int layoutX = line.fLeftToRight?
                        x + layoutPos : x + lineBound - layoutPos;
                int layoutY = y + line.fAscent;

                // Translating and then clipping doesn't work.  Try this:
                Rectangle2D.Float clipRect = new Rectangle2D.Float();
                clipRect.setRect(segment.fBounds);
                clipRect.x += layoutX;
                clipRect.y += layoutY;
                clipRect.width += 1;
                clipRect.height -= 1;

                Object savedClip = ClipWorkaround.saveClipState(g);
                try {
                    ClipWorkaround.translateAndDrawShapeWithClip(g,
                                                                layoutX,
                                                                layoutY,
                                                                clipRect,
                                                                carets[0]);
                    if (carets[1] != null) {
                        g.setColor(weakCaretColor);
                        ClipWorkaround.translateAndDrawShapeWithClip(g,
                                                                    layoutX,
                                                                    layoutY,
                                                                    clipRect,
                                                                    carets[1]);
                    }
                }
                finally {
                    ClipWorkaround.restoreClipState(g, savedClip);
                }
            }
            else {
                int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
                int endX = line.fLeftToRight? lineEnd : lineBound-lineEnd;
                endX += x;
                g.drawLine(endX, y, endX, y+line.getHeight()-1);
            }
        }
        finally {
            Graphics2DConversion.restoreColorState(g, savedPaint);
        }
    }

    private Rectangle caretBounds(BidiLayoutInfo line,
                                  MConstText text,
                                  int lengthBasis,
                                  int lineBound,
                                  int charOffset,
                                  int x,
                                  int y) {

        final int segmentCount = line.fSegments.size();
        final int lineStart = line.getCharStart(lengthBasis);
        int currentStart = lineStart;
        BidiSegment segment = null;
        int segmentIndex;

        for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
            segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
            int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
            if (currentEndpoint > charOffset) {
                break;
            }
            currentStart = currentEndpoint;
        }

        if (segmentIndex == segmentCount && segmentCount > 0) {
            // If we get here, line length is not 0, and charOffset is at end of line
            if (!isTab(text.at(charOffset-1))) {
                segmentIndex = segmentCount-1;
                segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
                currentStart = lineStart + line.getCharLength() -
                                    segment.fLayout.getCharacterCount();
            }
        }

        Rectangle r;
       
        if (segmentIndex < segmentCount) {
            TextLayout layout = segment.fLayout;
            int offsetInLayout = charOffset - currentStart;
            Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);
            r = carets[0].getBounds();
            if (carets[1] != null) {
                r.add(carets[1].getBounds());
            }
            r.width += 1;
           
            int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
            if (line.fLeftToRight) {
                r.x += layoutPos;
            }
            else {
                r.x += lineBound - layoutPos;
            }
            r.y += line.fAscent;
        }
        else {
            r = new Rectangle();
            r.height = line.getHeight();
            r.width = 1;
            int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
            if (line.fLeftToRight) {
                r.x = lineEnd;
            }
            else {
                r.x = lineBound - lineEnd;
            }
        }
       
        r.translate(x, y);
        return r;
    }

    private int strongCaretBaselinePosition(BidiLayoutInfo line,
                                            int lengthBasis,
                                            int lineBound,
                                            int charOffset) {

        final int segmentCount = line.fSegments.size();
        int currentStart = line.getCharStart(lengthBasis);
        BidiSegment segment = null;
        int segmentIndex;

        for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
            segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
            int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
            if (currentEndpoint > charOffset) {
                break;
            }
            currentStart = currentEndpoint;
        }

        if (segmentIndex < segmentCount) {
            TextLayout layout = segment.fLayout;
            int offsetInLayout = charOffset - currentStart;
            TextHitInfo hit = TextHitInfo.afterOffset(offsetInLayout);
            hit = TextLayout.DEFAULT_CARET_POLICY.getStrongCaret(hit, hit.getOtherHit(), layout);
            float[] info = layout.getCaretInfo(hit);
            int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;
            if (line.fLeftToRight) {
                return layoutPos + (int) info[0];
            }
            else {
                return lineBound - layoutPos + (int) info[0];
            }
        }
        else {
            int lineEnd = line.fLeadingMargin + line.fTotalAdvance;
            if (line.fLeftToRight) {
                return lineEnd;
            }
            else {
                return lineBound - lineEnd;
            }
        }
    }

    private int getNextOffset(BidiLayoutInfo line,
                              int lengthBasis,
                              int charOffset,
                              short dir) {

        if (dir != MFormatter.eLeft && dir != MFormatter.eRight) {
            throw new IllegalArgumentException("Invalid direction.");
        }

        // find segment containing offset:
        final int segmentCount = line.fSegments.size();
        final int lineCharStart = line.getCharStart(lengthBasis);

        int currentStart = lineCharStart;
        BidiSegment segment = null;
        int segmentIndex;

        for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {
            segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);
            int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();
            if (currentEndpoint > charOffset ||
                    (segmentIndex == segmentCount-1 && currentEndpoint==charOffset)) {
                break;
            }
            currentStart = currentEndpoint;
        }

        final boolean logAdvance = (dir==MFormatter.eRight)==(line.fLeftToRight);

        int result;

        if (segmentIndex < segmentCount) {
            TextLayout layout = segment.fLayout;
            int offsetInLayout = charOffset - currentStart;
            TextHitInfo hit = (dir==MFormatter.eLeft)?
                        layout.getNextLeftHit(offsetInLayout) :
                        layout.getNextRightHit(offsetInLayout);
            if (hit == null) {
                result = logAdvance?
                    currentStart+layout.getCharacterCount()+1 : currentStart-1;
            }
            else {
                result = hit.getInsertionIndex() + currentStart;
            }
        }
        else {
            result = logAdvance? lineCharStart + line.fCharLength + 1 :
                                         lineCharStart - 1;
        }

        return result;
    }
}
TOP

Related Classes of com.ibm.richtext.textformat.BidiParagraphRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.