1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Oleg V. Khaschansky
19 * @version $Revision$
20 *
21 */
22
23package org.apache.harmony.awt.gl.font;
24
25import java.awt.font.LineMetrics;
26import java.awt.font.GraphicAttribute;
27import java.awt.Font;
28import java.util.HashMap;
29import java.util.ArrayList;
30
31import org.apache.harmony.awt.internal.nls.Messages;
32
33/**
34 * This class operates with an arbitrary text string which can include
35 * any number of style, font and direction runs. It is responsible for computation
36 * of the text metrics, such as ascent, descent, leading and advance. Actually,
37 * each text run segment contains logic which allows it to compute its own metrics and
38 * responsibility of this class is to combine metrics for all segments included in the text,
39 * managed by the associated TextRunBreaker object.
40 */
41public class TextMetricsCalculator {
42    TextRunBreaker breaker; // Associated run breaker
43
44    // Metrics
45    float ascent = 0;
46    float descent = 0;
47    float leading = 0;
48    float advance = 0;
49
50    private float baselineOffsets[];
51    int baselineIndex;
52
53    public TextMetricsCalculator(TextRunBreaker breaker) {
54        this.breaker = breaker;
55        checkBaselines();
56    }
57
58    /**
59     * Returns either values cached by checkBaselines method or reasonable
60     * values for the TOP and BOTTOM alignments.
61     * @param baselineIndex - baseline index
62     * @return baseline offset
63     */
64    float getBaselineOffset(int baselineIndex) {
65        if (baselineIndex >= 0) {
66            return baselineOffsets[baselineIndex];
67        } else if (baselineIndex == GraphicAttribute.BOTTOM_ALIGNMENT) {
68            return descent;
69        } else if (baselineIndex == GraphicAttribute.TOP_ALIGNMENT) {
70            return -ascent;
71        } else {
72            // awt.3F=Invalid baseline index
73            throw new IllegalArgumentException(Messages.getString("awt.3F")); //$NON-NLS-1$
74        }
75    }
76
77    public float[] getBaselineOffsets() {
78        float ret[] = new float[baselineOffsets.length];
79        System.arraycopy(baselineOffsets, 0, ret, 0, baselineOffsets.length);
80        return ret;
81    }
82
83    /**
84     * Take baseline offsets from the first font or graphic attribute
85     * and normalizes them, than caches the results.
86     */
87    public void checkBaselines() {
88        // Take baseline offsets of the first font and normalize them
89        HashMap<Integer, Font> fonts = breaker.fonts;
90
91        Object val = fonts.get(new Integer(0));
92
93        if (val instanceof Font) {
94            Font firstFont = (Font) val;
95            LineMetrics lm = firstFont.getLineMetrics(breaker.text, 0, 1, breaker.frc);
96            baselineOffsets = lm.getBaselineOffsets();
97            baselineIndex = lm.getBaselineIndex();
98        } else if (val instanceof GraphicAttribute) {
99            // Get first graphic attribute and use it
100            GraphicAttribute ga = (GraphicAttribute) val;
101
102            int align = ga.getAlignment();
103
104            if (
105                    align == GraphicAttribute.TOP_ALIGNMENT ||
106                    align == GraphicAttribute.BOTTOM_ALIGNMENT
107            ) {
108                baselineIndex = GraphicAttribute.ROMAN_BASELINE;
109            } else {
110                baselineIndex = align;
111            }
112
113            baselineOffsets = new float[3];
114            baselineOffsets[0] = 0;
115            baselineOffsets[1] = (ga.getDescent() - ga.getAscent()) / 2.f;
116            baselineOffsets[2] = -ga.getAscent();
117        } else { // Use defaults - Roman baseline and zero offsets
118            baselineIndex = GraphicAttribute.ROMAN_BASELINE;
119            baselineOffsets = new float[3];
120        }
121
122        // Normalize offsets if needed
123        if (baselineOffsets[baselineIndex] != 0) {
124            float baseOffset = baselineOffsets[baselineIndex];
125            for (int i = 0; i < baselineOffsets.length; i++) {
126                baselineOffsets[i] -= baseOffset;
127            }
128        }
129    }
130
131    /**
132     * Computes metrics for the text managed by the associated TextRunBreaker
133     */
134    void computeMetrics() {
135
136        ArrayList<TextRunSegment> segments = breaker.runSegments;
137
138        float maxHeight = 0;
139        float maxHeightLeading = 0;
140
141        for (int i = 0; i < segments.size(); i++) {
142            TextRunSegment segment = segments.get(i);
143            BasicMetrics metrics = segment.metrics;
144            int baseline = metrics.baseLineIndex;
145
146            if (baseline >= 0) {
147                float baselineOffset = baselineOffsets[metrics.baseLineIndex];
148                float fixedDescent = metrics.descent + baselineOffset;
149
150                ascent = Math.max(ascent, metrics.ascent - baselineOffset);
151                descent = Math.max(descent, fixedDescent);
152                leading = Math.max(leading, fixedDescent + metrics.leading);
153            } else { // Position is not fixed by the baseline, need sum of ascent and descent
154                float height = metrics.ascent + metrics.descent;
155
156                maxHeight = Math.max(maxHeight, height);
157                maxHeightLeading = Math.max(maxHeightLeading, height + metrics.leading);
158            }
159        }
160
161        // Need to increase sizes for graphics?
162        if (maxHeightLeading != 0) {
163            descent = Math.max(descent, maxHeight - ascent);
164            leading = Math.max(leading, maxHeightLeading - ascent);
165        }
166
167        // Normalize leading
168        leading -= descent;
169
170        BasicMetrics currMetrics;
171        float currAdvance = 0;
172
173        for (int i = 0; i < segments.size(); i++) {
174            TextRunSegment segment = segments.get(breaker.getSegmentFromVisualOrder(i));
175            currMetrics = segment.metrics;
176
177            segment.y = getBaselineOffset(currMetrics.baseLineIndex)
178                    + currMetrics.superScriptOffset;
179            segment.x = currAdvance;
180
181            currAdvance += segment.getAdvance();
182        }
183
184        advance = currAdvance;
185    }
186
187    /**
188     * Computes metrics and creates BasicMetrics object from them
189     * @return basic metrics
190     */
191    public BasicMetrics createMetrics() {
192        computeMetrics();
193        return new BasicMetrics(this);
194    }
195
196    /**
197     * Corrects advance after justification. Gets BasicMetrics object
198     * and updates advance stored into it.
199     * @param metrics - metrics with outdated advance which should be corrected
200     */
201    public void correctAdvance(BasicMetrics metrics) {
202        ArrayList<TextRunSegment> segments = breaker.runSegments;
203        TextRunSegment segment = segments.get(breaker
204                .getSegmentFromVisualOrder(segments.size() - 1));
205
206        advance = segment.x + segment.getAdvance();
207        metrics.advance = advance;
208    }
209}
210