All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.harmony.awt.gl.font.TextRunSegmentImpl Maven / Gradle / Ivy

The newest version!
/*
 *  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
 *
 */

package org.apache.harmony.awt.gl.font;

// XXX - TODO - bidi not implemented yet
//import java.text.Bidi;
import java.util.Arrays;

import org.apache.harmony.awt.internal.nls.Messages;

import com.google.code.appengine.awt.*;
import com.google.code.appengine.awt.font.*;
import com.google.code.appengine.awt.geom.AffineTransform;
import com.google.code.appengine.awt.geom.GeneralPath;
import com.google.code.appengine.awt.geom.Point2D;
import com.google.code.appengine.awt.geom.Rectangle2D;


/**
 * Date: Apr 25, 2005
 * Time: 4:33:18 PM
 *
 * This class contains the implementation of the behavior of the
 * text run segment with constant text attributes and direction.
 */
public class TextRunSegmentImpl {

    /**
     * This class contains basic information required for creation
     * of the glyph-based text run segment.
     */
    public static class TextSegmentInfo {
        // XXX - TODO - bidi not implemented yet
        //Bidi bidi;

        Font font;
        FontRenderContext frc;

        char text[];

        int start;
        int end;
        int length;

        int flags = 0;

        byte level = 0;

        TextSegmentInfo(
                byte level,
                Font font, FontRenderContext frc,
                char text[], int start, int end
        ) {
            this.font = font;
            this.frc = frc;
            this.text = text;
            this.start = start;
            this.end = end;
            this.level = level;
            length = end - start;
        }
    }

    /**
     * This class represents a simple text segment backed by the glyph vector
     */
    public static class TextRunSegmentCommon extends TextRunSegment {
        TextSegmentInfo info;
        private GlyphVector gv;
        private float advanceIncrements[];
        private int char2glyph[];
        private GlyphJustificationInfo gjis[]; // Glyph justification info

        TextRunSegmentCommon(TextSegmentInfo i, TextDecorator.Decoration d) {
            // XXX - todo - check support bidi
            i.flags &= ~0x09; // Clear bidi flags

            if ((i.level & 0x1) != 0) {
                i.flags |= Font.LAYOUT_RIGHT_TO_LEFT;
            }

            info = i;
            this.decoration = d;

            LineMetrics lm = i.font.getLineMetrics(i.text, i.start, i.end, i.frc);
            this.metrics = new BasicMetrics(lm, i.font);

            if (lm.getNumChars() != i.length) { // XXX todo - This should be handled
                // awt.41=Font returned unsupported type of line metrics. This case is known, but not supported yet.
                throw new UnsupportedOperationException(
                        Messages.getString("awt.41")); //$NON-NLS-1$
            }
        }

        @Override
        public Object clone() {
            return new TextRunSegmentCommon(info, decoration);
        }

        /**
         * Creates glyph vector from the managed text if needed
         * @return glyph vector
         */
        private GlyphVector getGlyphVector() {
            if (gv==null) {
                gv = info.font.layoutGlyphVector(
                        info.frc,
                        info.text,
                        info.start,
                        info.end - info.start, // NOTE: This parameter violates
                                               // spec, it is count,
                                               // not limit as spec states
                        info.flags
                );
            }

            return gv;
        }

        /**
         * Renders this text run segment
         * @param g2d - graphics to render to
         * @param xOffset - X offset from the graphics origin to the
         * origin of the text layout
         * @param yOffset - Y offset from the graphics origin to the
         * origin of the text layout
         */
        @Override
        void draw(Graphics2D g2d, float xOffset, float yOffset) {
            if (decoration == null) {
                g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y);
            } else {
                TextDecorator.prepareGraphics(this, g2d, xOffset, yOffset);
                g2d.drawGlyphVector(getGlyphVector(), xOffset + x, yOffset + y);
                TextDecorator.drawTextDecorations(this, g2d, xOffset, yOffset);
                TextDecorator.restoreGraphics(decoration, g2d);
            }
        }

        /**
         * Returns visual bounds of this segment
         * @return visual bounds
         */
        @Override
        Rectangle2D getVisualBounds() {
            if (visualBounds == null) {
                visualBounds =
                        TextDecorator.extendVisualBounds(
                                this,
                                getGlyphVector().getVisualBounds(),
                                decoration
                        );

                visualBounds.setRect(
                        x + visualBounds.getX(),
                        y + visualBounds.getY(),
                        visualBounds.getWidth(),
                        visualBounds.getHeight()
                );
            }

            return (Rectangle2D) visualBounds.clone();
        }

        /**
         * Returns logical bounds of this segment
         * @return logical bounds
         */
        @Override
        Rectangle2D getLogicalBounds() {
            if (logicalBounds == null) {
                logicalBounds = getGlyphVector().getLogicalBounds();

                logicalBounds.setRect(
                        x + logicalBounds.getX(),
                        y + logicalBounds.getY(),
                        logicalBounds.getWidth(),
                        logicalBounds.getHeight()
                );
            }

            return (Rectangle2D) logicalBounds.clone();
        }

        @Override
        float getAdvance() {
            return (float) getLogicalBounds().getWidth();
        }

        /**
         * Attempts to map each character to the corresponding advance increment
         */
        void initAdvanceMapping() {
            GlyphVector gv = getGlyphVector();
            int charIndicies[] = gv.getGlyphCharIndices(0, gv.getNumGlyphs(), null);
            advanceIncrements = new float[info.length];

            for (int i=0; i info.length) {
                end = info.length;
            }

            float sum = 0;
            for (int i=start; i info.length) {
                limit = info.length;
            }

            GeneralPath result = new GeneralPath();

            int glyphIndex = 0;

            for (int i=start; i info.length) {
                index = info.length;
            }

            float result = 0;

            int glyphIndex = getChar2Glyph()[index];
            result = (float) getGlyphVector().getGlyphPosition(glyphIndex).getX();

            // Shift to the segment's coordinates
            result += x;

            return result;
        }

        /**
         * Returns the advance of the individual character
         * @param index - character index
         * @return character advance
         */
        @Override
        float getCharAdvance(int index) {
            if (advanceIncrements == null) {
                initAdvanceMapping();
            }

            return advanceIncrements[index - this.getStart()];
        }

        /**
         * Returns the outline shape
         * @return outline
         */
        @Override
        Shape getOutline() {
            AffineTransform t = AffineTransform.getTranslateInstance(x, y);
            return t.createTransformedShape(
                    TextDecorator.extendOutline(
                            this,
                            getGlyphVector().getOutline(),
                            decoration
                    )
            );
        }

        /**
         * Checks if the character doesn't contribute to the text advance
         * @param index - character index
         * @return true if the character has zero advance
         */
        @Override
        boolean charHasZeroAdvance(int index) {
            if (advanceIncrements == null) {
                initAdvanceMapping();
            }

            return advanceIncrements[index - this.getStart()] == 0;
        }

        /**
         * Creates text hit info from the hit position
         * @param hitX - X coordinate relative to the origin of the layout
         * @param hitY - Y coordinate relative to the origin of the layout
         * @return hit info
         */
        @Override
        TextHitInfo hitTest(float hitX, float hitY) {
            hitX -= x;

            float glyphPositions[] =
                    getGlyphVector().getGlyphPositions(0, info.length+1, null);

            int glyphIdx;
            boolean leading = false;
            for (glyphIdx = 1; glyphIdx <= info.length; glyphIdx++) {
                if (glyphPositions[(glyphIdx)*2] >= hitX) {
                    float advance =
                            glyphPositions[(glyphIdx)*2] - glyphPositions[(glyphIdx-1)*2];
                    leading = glyphPositions[(glyphIdx-1)*2] + advance/2 > hitX ? true : false;
                    glyphIdx--;
                    break;
                }
            }

            if (glyphIdx == info.length) {
                glyphIdx--;
            }

            int charIdx = getGlyphVector().getGlyphCharIndex(glyphIdx);

            return (leading) ^ ((info.level & 0x1) == 0x1)?
                    TextHitInfo.leading(charIdx + info.start) :
                    TextHitInfo.trailing(charIdx + info.start);
        }

        /**
         * Collects GlyphJustificationInfo objects from the glyph vector
         * @return array of all GlyphJustificationInfo objects
         */
        private GlyphJustificationInfo[] getGlyphJustificationInfos() {
            if (gjis == null) {
                GlyphVector gv = getGlyphVector();
                int nGlyphs = gv.getNumGlyphs();
                int charIndicies[] = gv.getGlyphCharIndices(0, nGlyphs, null);
                gjis = new GlyphJustificationInfo[nGlyphs];

                // Patch: temporary patch, getGlyphJustificationInfo is not implemented
                float fontSize = info.font.getSize2D();
                GlyphJustificationInfo defaultInfo =
                        new GlyphJustificationInfo(
                                0, // weight
                                false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0, // grow
                                false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0); // shrink
                GlyphJustificationInfo spaceInfo = new GlyphJustificationInfo(
                        fontSize, // weight
                        true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize, // grow
                        true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, fontSize); // shrink

                ////////
                // Temporary patch, getGlyphJustificationInfo is not implemented
                for (int i = 0; i < nGlyphs; i++) {
                    //gjis[i] = getGlyphVector().getGlyphJustificationInfo(i);

                    char c = info.text[charIndicies[i] + info.start];
                    if (Character.isWhitespace(c)) {
                        gjis[i] = spaceInfo;
                    } else {
                        gjis[i] = defaultInfo;
                    }
                    // End patch
                }
            }

            return gjis;
        }

        /**
         * Collects justification information into JustificationInfo object
         * @param jInfo - JustificationInfo object
         */
        @Override
        void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) {
            int lastChar = Math.min(jInfo.lastIdx, info.end) - info.start;
            boolean haveFirst = info.start <= jInfo.firstIdx;
            boolean haveLast = info.end >= (jInfo.lastIdx + 1);

            int prevGlyphIdx = -1;
            int currGlyphIdx;

            if (jInfo.grow) { // Check how much we can grow/shrink on current priority level
                for (int i=0; i 0 ? jInfos[lastPriority] : null;

            boolean haveFirst = info.start <= firstInfo.firstIdx;
            boolean haveLast = info.end >= (firstInfo.lastIdx + 1);

            // Here we suppose that GLYPHS are ordered LEFT TO RIGHT
            int firstGlyph = haveFirst ?
                    getChar2Glyph()[firstInfo.firstIdx - info.start] :
                    getChar2Glyph()[0];

            int lastGlyph = haveLast ?
                    getChar2Glyph()[firstInfo.lastIdx - info.start] :
                    getChar2Glyph()[info.length - 1];
            if (haveLast) {
                lastGlyph--;
            }

            TextRunBreaker.JustificationInfo currInfo;
            float glyphOffset = 0;
            float positionIncrement = 0;
            float sideIncrement = 0;

            if (haveFirst) {  // Don't add padding before first char
                GlyphJustificationInfo gji = getGlyphJustificationInfos()[firstGlyph];
                currInfo = jInfos[gji.growPriority];
                if (currInfo != null) {
                    if (currInfo.useLimits) {
                        if (currInfo.absorb) {
                            glyphOffset += gji.weight * currInfo.absorbedGapPerUnit;
                        } else if (
                                lastInfo != null &&
                                lastInfo.priority == currInfo.priority
                        ) {
                            glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit;
                        }
                        glyphOffset +=
                                firstInfo.grow ?
                                gji.growRightLimit :
                                -gji.shrinkRightLimit;
                    } else {
                        glyphOffset += gji.weight * currInfo.gapPerUnit;
                    }
                }

                firstGlyph++;
            }

            if (firstInfo.grow) {
                for (int i=firstGlyph; i<=lastGlyph; i++) {
                    GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
                    currInfo = jInfos[gji.growPriority];
                    if (currInfo == null) {
                        // We still have to increment glyph position
                        Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
                        glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
                        getGlyphVector().setGlyphPosition(i, glyphPos);

                        continue;
                    }

                    if (currInfo.useLimits) {
                        glyphOffset += gji.growLeftLimit;
                        if (currInfo.absorb) {
                            sideIncrement = gji.weight * currInfo.absorbedGapPerUnit;
                            glyphOffset += sideIncrement;
                            positionIncrement = glyphOffset;
                            glyphOffset += sideIncrement;
                        } else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
                            sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit;
                            glyphOffset += sideIncrement;
                            positionIncrement = glyphOffset;
                            glyphOffset += sideIncrement;
                        } else {
                            positionIncrement = glyphOffset;
                        }
                        glyphOffset += gji.growRightLimit;
                    } else {
                        sideIncrement = gji.weight * currInfo.gapPerUnit;
                        glyphOffset += sideIncrement;
                        positionIncrement = glyphOffset;
                        glyphOffset += sideIncrement;
                    }

                    Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
                    glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY());
                    getGlyphVector().setGlyphPosition(i, glyphPos);
                }
            } else {
                for (int i=firstGlyph; i<=lastGlyph; i++) {
                    GlyphJustificationInfo gji = getGlyphJustificationInfos()[i];
                    currInfo = jInfos[gji.shrinkPriority];
                    if (currInfo == null) {
                        // We still have to increment glyph position
                        Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
                        glyphPos.setLocation(glyphPos.getX() + glyphOffset, glyphPos.getY());
                        getGlyphVector().setGlyphPosition(i, glyphPos);

                        continue;
                    }

                    if (currInfo.useLimits) {
                        glyphOffset -= gji.shrinkLeftLimit;
                        if (currInfo.absorb) {
                            sideIncrement = gji.weight * currInfo.absorbedGapPerUnit;
                            glyphOffset += sideIncrement;
                            positionIncrement = glyphOffset;
                            glyphOffset += sideIncrement;
                        } else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
                            sideIncrement = gji.weight * lastInfo.absorbedGapPerUnit;
                            glyphOffset += sideIncrement;
                            positionIncrement = glyphOffset;
                            glyphOffset += sideIncrement;
                        } else {
                            positionIncrement = glyphOffset;
                        }
                        glyphOffset -= gji.shrinkRightLimit;
                    } else {
                        sideIncrement =  gji.weight * currInfo.gapPerUnit;
                        glyphOffset += sideIncrement;
                        positionIncrement = glyphOffset;
                        glyphOffset += sideIncrement;
                    }

                    Point2D glyphPos = getGlyphVector().getGlyphPosition(i);
                    glyphPos.setLocation(glyphPos.getX() + positionIncrement, glyphPos.getY());
                    getGlyphVector().setGlyphPosition(i, glyphPos);
                }
            }


            if (haveLast) {   // Don't add padding after last char
                lastGlyph++;

                GlyphJustificationInfo gji = getGlyphJustificationInfos()[lastGlyph];
                currInfo = jInfos[gji.growPriority];

                if (currInfo != null) {
                    if (currInfo.useLimits) {
                        glyphOffset += firstInfo.grow ? gji.growLeftLimit : -gji.shrinkLeftLimit;
                        if (currInfo.absorb) {
                            glyphOffset += gji.weight * currInfo.absorbedGapPerUnit;
                        } else if (lastInfo != null && lastInfo.priority == currInfo.priority) {
                            glyphOffset += gji.weight * lastInfo.absorbedGapPerUnit;
                        }
                    } else {
                        glyphOffset += gji.weight * currInfo.gapPerUnit;
                    }
                }

                // Ajust positions of all glyphs after last glyph
                for (int i=lastGlyph; i length) {
                return length + this.start;
            }
            return charOffset + start + this.start;
        }

        @Override
        int getStart() {
            return start;
        }

        @Override
        int getEnd() {
            return start + length;
        }

        @Override
        int getLength() {
            return length;
        }

        @Override
        Shape getCharsBlackBoxBounds(int start, int limit) {
            start -= this.start;
            limit -= this.start;

            if (limit > length) {
                limit = length;
            }

            Rectangle2D charBounds = ga.getBounds();
            charBounds.setRect(
                    charBounds.getX() + ga.getAdvance() * start + x,
                    charBounds.getY() + y,
                    charBounds.getWidth() + ga.getAdvance() * (limit - start),
                    charBounds.getHeight()
            );

            return charBounds;
        }

        @Override
        float getCharPosition(int index) {
            index -= start;
            if (index > length) {
                index = length;
            }

            return ga.getAdvance() * index + x;
        }

        @Override
        float getCharAdvance(int index) {
            return ga.getAdvance();
        }

        @Override
        Shape getOutline() {
            AffineTransform t = AffineTransform.getTranslateInstance(x, y);
            return t.createTransformedShape(
                    TextDecorator.extendOutline(this, getVisualBounds(), decoration)
            );
        }

        @Override
        boolean charHasZeroAdvance(int index) {
            return false;
        }

        @Override
        TextHitInfo hitTest(float hitX, float hitY) {
            hitX -= x;

            float tmp = hitX / ga.getAdvance();
            int hitIndex = Math.round(tmp);

            if (tmp > hitIndex) {
                return TextHitInfo.leading(hitIndex + this.start);
            }
            return TextHitInfo.trailing(hitIndex + this.start);
        }

        @Override
        void updateJustificationInfo(TextRunBreaker.JustificationInfo jInfo) {
            // Do nothing
        }

        @Override
        float doJustification(TextRunBreaker.JustificationInfo jInfos[]) {
            // Do nothing
            return 0;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy