Path: blob/master/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java
41155 views
/*1* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24/*25*26* (C) Copyright IBM Corp. 1998-2003 - All Rights Reserved27*/2829package sun.font;3031import java.awt.Font;32import java.awt.Graphics2D;33import java.awt.Rectangle;34import java.awt.Shape;3536import java.awt.font.FontRenderContext;37import java.awt.font.GlyphJustificationInfo;38import java.awt.font.GlyphMetrics;39import java.awt.font.LineMetrics;40import java.awt.font.TextAttribute;4142import java.awt.geom.AffineTransform;43import java.awt.geom.Point2D;44import java.awt.geom.Rectangle2D;4546import java.util.Map;4748/**49* Default implementation of ExtendedTextLabel.50*/5152// {jbr} I made this class package-private to keep the53// Decoration.Label API package-private.5455/* public */56class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.Label {5758TextSource source;59private Decoration decorator;6061// caches62private Font font;63private AffineTransform baseTX;64private CoreMetrics cm;6566Rectangle2D lb;67Rectangle2D ab;68Rectangle2D vb;69Rectangle2D ib;70StandardGlyphVector gv;71float[] charinfo;7273/**74* Create from a TextSource.75*/76public ExtendedTextSourceLabel(TextSource source, Decoration decorator) {77this.source = source;78this.decorator = decorator;79finishInit();80}8182/**83* Create from a TextSource, optionally using cached data from oldLabel starting at the offset.84* If present oldLabel must have been created from a run of text that includes the text used in85* the new label. Start in source corresponds to logical character offset in oldLabel.86*/87public ExtendedTextSourceLabel(TextSource source, ExtendedTextSourceLabel oldLabel, int offset) {88// currently no optimization.89this.source = source;90this.decorator = oldLabel.decorator;91finishInit();92}9394private void finishInit() {95font = source.getFont();9697Map<TextAttribute, ?> atts = font.getAttributes();98baseTX = AttributeValues.getBaselineTransform(atts);99if (baseTX == null){100cm = source.getCoreMetrics();101} else {102AffineTransform charTX = AttributeValues.getCharTransform(atts);103if (charTX == null) {104charTX = new AffineTransform();105}106font = font.deriveFont(charTX);107108LineMetrics lm = font.getLineMetrics(source.getChars(), source.getStart(),109source.getStart() + source.getLength(), source.getFRC());110cm = CoreMetrics.get(lm);111}112}113114115// TextLabel API116117public Rectangle2D getLogicalBounds() {118return getLogicalBounds(0, 0);119}120121public Rectangle2D getLogicalBounds(float x, float y) {122if (lb == null) {123lb = createLogicalBounds();124}125return new Rectangle2D.Float((float)(lb.getX() + x),126(float)(lb.getY() + y),127(float)lb.getWidth(),128(float)lb.getHeight());129}130131public float getAdvance() {132if (lb == null) {133lb = createLogicalBounds();134}135return (float)lb.getWidth();136}137138public Rectangle2D getVisualBounds(float x, float y) {139if (vb == null) {140vb = decorator.getVisualBounds(this);141}142return new Rectangle2D.Float((float)(vb.getX() + x),143(float)(vb.getY() + y),144(float)vb.getWidth(),145(float)vb.getHeight());146}147148public Rectangle2D getAlignBounds(float x, float y) {149if (ab == null) {150ab = createAlignBounds();151}152return new Rectangle2D.Float((float)(ab.getX() + x),153(float)(ab.getY() + y),154(float)ab.getWidth(),155(float)ab.getHeight());156157}158159public Rectangle2D getItalicBounds(float x, float y) {160if (ib == null) {161ib = createItalicBounds();162}163return new Rectangle2D.Float((float)(ib.getX() + x),164(float)(ib.getY() + y),165(float)ib.getWidth(),166(float)ib.getHeight());167168}169170public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {171return getGV().getPixelBounds(frc, x, y);172}173174public boolean isSimple() {175return decorator == Decoration.getPlainDecoration() &&176baseTX == null;177}178179public AffineTransform getBaselineTransform() {180return baseTX; // passing internal object, caller must not modify!181}182183public Shape handleGetOutline(float x, float y) {184return getGV().getOutline(x, y);185}186187public Shape getOutline(float x, float y) {188return decorator.getOutline(this, x, y);189}190191public void handleDraw(Graphics2D g, float x, float y) {192g.drawGlyphVector(getGV(), x, y);193}194195public void draw(Graphics2D g, float x, float y) {196decorator.drawTextAndDecorations(this, g, x, y);197}198199/**200* The logical bounds extends from the origin of the glyphvector to the201* position at which a following glyphvector's origin should be placed.202* We always assume glyph vectors are rendered from left to right, so203* the origin is always to the left.204* <p> On a left-to-right run, combining marks and 'ligatured away'205* characters are to the right of their base characters. The charinfo206* array will record the character positions for these 'missing' characters207* as being at the origin+advance of the base glyph, with zero advance.208* (This is not necessarily the same as the glyph position, for example,209* an umlaut glyph may have a position to the left of this point, it depends210* on whether the font was designed so that such glyphs overhang to the left211* of their origin, or whether it presumes some kind of kerning to position212* the glyphs). Anyway, the left of the bounds is the origin of the first213* logical (leftmost) character, and the right is the origin + advance of the214* last logical (rightmost) character.215* <p> On a right-to-left run, these special characters are to the left216* of their base characters. Again, since 'glyph position' has been abstracted217* away, we can use the origin of the leftmost character, and the origin +218* advance of the rightmost character.219* <p> On a mixed run (hindi) we can't rely on the first logical character220* being the leftmost character. However we can again rely on the leftmost221* character origin and the rightmost character + advance.222*/223protected Rectangle2D createLogicalBounds() {224return getGV().getLogicalBounds();225}226227public Rectangle2D handleGetVisualBounds() {228return getGV().getVisualBounds();229}230231/**232* Like createLogicalBounds except ignore leading and logically trailing white space.233* this assumes logically trailing whitespace is also visually trailing.234* Whitespace is anything that has a zero visual width, regardless of its advance.235* <p> We make the same simplifying assumptions as in createLogicalBounds, namely236* that we can rely on the charinfo to shield us from any glyph positioning oddities237* in the font that place the glyph for a character at other than the pos + advance238* of the character to its left. So we no longer need to skip chars with zero239* advance, as their bounds (right and left) are already correct.240*/241protected Rectangle2D createAlignBounds() {242float[] info = getCharinfo();243244float al = 0f;245float at = -cm.ascent;246float aw = 0f;247float ah = cm.ascent + cm.descent;248249if (charinfo == null || charinfo.length == 0) {250return new Rectangle2D.Float(al, at, aw, ah);251}252253boolean lineIsLTR = (source.getLayoutFlags() & 0x8) == 0;254int rn = info.length - numvals;255if (lineIsLTR) {256while (rn > 0 && info[rn+visw] == 0) {257rn -= numvals;258}259}260261if (rn >= 0) {262int ln = 0;263while (ln < rn && ((info[ln+advx] == 0) || (!lineIsLTR && info[ln+visw] == 0))) {264ln += numvals;265}266267al = Math.max(0f, info[ln+posx]);268aw = info[rn+posx] + info[rn+advx] - al;269}270271/*272boolean lineIsLTR = source.lineIsLTR();273int rn = info.length - numvals;274while (rn > 0 && ((info[rn+advx] == 0) || (lineIsLTR && info[rn+visw] == 0))) {275rn -= numvals;276}277278if (rn >= 0) {279int ln = 0;280while (ln < rn && ((info[ln+advx] == 0) || (!lineIsLTR && info[ln+visw] == 0))) {281ln += numvals;282}283284al = Math.max(0f, info[ln+posx]);285aw = info[rn+posx] + info[rn+advx] - al;286}287*/288289return new Rectangle2D.Float(al, at, aw, ah);290}291292public Rectangle2D createItalicBounds() {293float ia = cm.italicAngle;294295Rectangle2D lb = getLogicalBounds();296float l = (float)lb.getMinX();297float t = -cm.ascent;298float r = (float)lb.getMaxX();299float b = cm.descent;300if (ia != 0) {301if (ia > 0) {302l -= ia * (b - cm.ssOffset);303r -= ia * (t - cm.ssOffset);304} else {305l -= ia * (t - cm.ssOffset);306r -= ia * (b - cm.ssOffset);307}308}309return new Rectangle2D.Float(l, t, r - l, b - t);310}311312private StandardGlyphVector getGV() {313if (gv == null) {314gv = createGV();315}316317return gv;318}319320protected StandardGlyphVector createGV() {321FontRenderContext frc = source.getFRC();322int flags = source.getLayoutFlags();323char[] context = source.getChars();324int start = source.getStart();325int length = source.getLength();326327GlyphLayout gl = GlyphLayout.get(null); // !!! no custom layout engines328gv = gl.layout(font, frc, context, start, length, flags, null); // ??? use textsource329GlyphLayout.done(gl);330331return gv;332}333334// ExtendedTextLabel API335336private static final int posx = 0,337posy = 1,338advx = 2,339advy = 3,340visx = 4,341visy = 5,342visw = 6,343vish = 7;344private static final int numvals = 8;345346public int getNumCharacters() {347return source.getLength();348}349350public CoreMetrics getCoreMetrics() {351return cm;352}353354public float getCharX(int index) {355validate(index);356float[] charinfo = getCharinfo();357int idx = l2v(index) * numvals + posx;358if (charinfo == null || idx >= charinfo.length) {359return 0f;360} else {361return charinfo[idx];362}363}364365public float getCharY(int index) {366validate(index);367float[] charinfo = getCharinfo();368int idx = l2v(index) * numvals + posy;369if (charinfo == null || idx >= charinfo.length) {370return 0f;371} else {372return charinfo[idx];373}374}375376public float getCharAdvance(int index) {377validate(index);378float[] charinfo = getCharinfo();379int idx = l2v(index) * numvals + advx;380if (charinfo == null || idx >= charinfo.length) {381return 0f;382} else {383return charinfo[idx];384}385}386387public Rectangle2D handleGetCharVisualBounds(int index) {388validate(index);389float[] charinfo = getCharinfo();390index = l2v(index) * numvals;391if (charinfo == null || (index+vish) >= charinfo.length) {392return new Rectangle2D.Float();393}394return new Rectangle2D.Float(395charinfo[index + visx],396charinfo[index + visy],397charinfo[index + visw],398charinfo[index + vish]);399}400401public Rectangle2D getCharVisualBounds(int index, float x, float y) {402403Rectangle2D bounds = decorator.getCharVisualBounds(this, index);404if (x != 0 || y != 0) {405bounds.setRect(bounds.getX()+x,406bounds.getY()+y,407bounds.getWidth(),408bounds.getHeight());409}410return bounds;411}412413private void validate(int index) {414if (index < 0) {415throw new IllegalArgumentException("index " + index + " < 0");416} else if (index >= source.getLength()) {417throw new IllegalArgumentException("index " + index + " < " + source.getLength());418}419}420421/*422public int hitTestChar(float x, float y) {423// !!! return index of char hit, for swing424// result is negative for trailing-edge hits425// no italics so no problem at margins.426// for now, ignore y since we assume horizontal text427428// find non-combining char origin to right of x429float[] charinfo = getCharinfo();430431int n = 0;432int e = source.getLength();433while (n < e && charinfo[n + advx] != 0 && charinfo[n + posx] > x) {434n += numvals;435}436float rightx = n < e ? charinfo[n+posx] : charinfo[e - numvals + posx] + charinfo[e - numvals + advx];437438// find non-combining char to left of that char439n -= numvals;440while (n >= 0 && charinfo[n+advx] == 0) {441n -= numvals;442}443float leftx = n >= 0 ? charinfo[n+posx] : 0;444float lefta = n >= 0 ? charinfo[n+advx] : 0;445446n /= numvals;447448boolean left = true;449if (x < leftx + lefta / 2f) {450// left of prev char451} else if (x < (leftx + lefta + rightx) / 2f) {452// right of prev char453left = false;454} else {455// left of follow char456n += 1;457}458459if ((source.getLayoutFlags() & 0x1) != 0) {460n = getNumCharacters() - 1 - n;461left = !left;462}463464return left ? n : -n;465}466*/467468public int logicalToVisual(int logicalIndex) {469validate(logicalIndex);470return l2v(logicalIndex);471}472473public int visualToLogical(int visualIndex) {474validate(visualIndex);475return v2l(visualIndex);476}477478public int getLineBreakIndex(int start, float width) {479float[] charinfo = getCharinfo();480int length = source.getLength();481--start;482while (width >= 0 && ++start < length) {483int cidx = l2v(start) * numvals + advx;484if (cidx >= charinfo.length) {485break; // layout bailed for some reason486}487float adv = charinfo[cidx];488width -= adv;489}490491return start;492}493494public float getAdvanceBetween(int start, int limit) {495float a = 0f;496497float[] charinfo = getCharinfo();498--start;499while (++start < limit) {500int cidx = l2v(start) * numvals + advx;501if (cidx >= charinfo.length) {502break; // layout bailed for some reason503}504a += charinfo[cidx];505}506507return a;508}509510public boolean caretAtOffsetIsValid(int offset) {511// REMIND: improve this implementation512513// Ligature formation can either be done in logical order,514// with the ligature glyph logically preceding the null515// chars; or in visual order, with the ligature glyph to516// the left of the null chars. This method's implementation517// must reflect which strategy is used.518519if (offset == 0 || offset == source.getLength()) {520return true;521}522char c = source.getChars()[source.getStart() + offset];523if (c == '\t' || c == '\n' || c == '\r') { // hack524return true;525}526int v = l2v(offset);527528// If ligatures are always to the left, do this stuff:529//if (!(source.getLayoutFlags() & 0x1) == 0) {530// v += 1;531// if (v == source.getLength()) {532// return true;533// }534//}535536int idx = v * numvals + advx;537float[] charinfo = getCharinfo();538if (charinfo == null || idx >= charinfo.length) {539return false;540} else {541return charinfo[idx] != 0;542}543}544545private float[] getCharinfo() {546if (charinfo == null) {547charinfo = createCharinfo();548}549return charinfo;550}551552private static final boolean DEBUG = FontUtilities.debugFonts();553/*554* This takes the glyph info record obtained from the glyph vector and converts it into a similar record555* adjusted to represent character data instead. For economy we don't use glyph info records in this processing.556*557* Here are some constraints:558* - there can be more glyphs than characters (glyph insertion, perhaps based on normalization, has taken place)559* - there can be fewer glyphs than characters560* Some layout engines may insert 0xffff glyphs for characters ligaturized away, but561* not all do, and it cannot be relied upon.562* - each glyph maps to a single character, when multiple glyphs exist for a character they all map to it, but563* no two characters map to the same glyph564* - multiple glyphs mapping to the same character need not be in sequence (thai, tamil have split characters)565* - glyphs may be arbitrarily reordered (Indic reorders glyphs)566* - all glyphs share the same bidi level567* - all glyphs share the same horizontal (or vertical) baseline568* - combining marks visually follow their base character in the glyph array-- i.e. in an rtl gv they are569* to the left of their base character-- and have zero advance.570*571* The output maps this to character positions, and therefore caret positions, via the following assumptions:572* - zero-advance glyphs do not contribute to the advance of their character (i.e. position is ignored), conversely573* if a glyph is to contribute to the advance of its character it must have a non-zero (float) advance574* - no carets can appear between a zero width character and its preceding character, where 'preceding' is575* defined logically.576* - no carets can appear within a split character577* - no carets can appear within a local reordering (i.e. Indic reordering, or non-adjacent split characters)578* - all characters lie on the same baseline, and it is either horizontal or vertical579* - the charinfo is in uniform ltr or rtl order (visual order), since local reorderings and split characters are removed580*581* The algorithm works in the following way:582* 1) we scan the glyphs ltr or rtl based on the bidi run direction583* 2) Since the may be fewer glyphs than chars we cannot work in place.584* A new array is allocated for output.585* a) if the line is ltr, we start writing at position 0 until we finish, there may be leftver space586* b) if the line is rtl and 1-1, we start writing at position numChars/glyphs - 1 until we finish at 0587* c) otherwise if we don't finish at 0, we have to copy the data down588* 3) we consume clusters in the following way:589* a) the first element is always consumed590* b) subsequent elements are consumed if:591* i) their advance is zero592* ii) their character index <= the character index of any character seen in this cluster593* iii) the minimum character index seen in this cluster isn't adjacent to the previous cluster594* c) character data is written as follows for horizontal lines (x/y and w/h are exchanged on vertical lines)595* i) the x position is the position of the leftmost glyph whose advance is not zero596* ii)the y position is the baseline597* iii) the x advance is the distance to the maximum x + adv of all glyphs whose advance is not zero598* iv) the y advance is the baseline599* v) vis x,y,w,h tightly encloses the vis x,y,w,h of all the glyphs with nonzero w and h600* 4) In the future, we can make some simple optimizations to avoid copying if we know some things:601* a) if the mapping is 1-1, unidirectional, and there are no zero-adv glyphs, we just return the glyphinfo602* b) if the mapping is 1-1, unidirectional, we just adjust the remaining glyphs to originate at right/left of the base603* c) if the mapping is 1-1, we compute the base position and advance as we go, then go back to adjust the remaining glyphs604* d) otherwise we keep separate track of the write position as we do (c) since no glyph in the cluster may be in the605* position we are writing.606* e) most clusters are simply the single base glyph in the same position as its character, so we try to avoid607* copying its data unnecessarily.608* 5) the glyph vector ought to provide access to these 'global' attributes to enable these optimizations. A single609* int with flags set is probably ok, we could also provide accessors for each attribute. This doesn't map to610* the GlyphMetrics flags very well, so I won't attempt to keep them similar. It might be useful to add those611* in addition to these.612* int FLAG_HAS_ZERO_ADVANCE_GLYPHS = 1; // set if there are zero-advance glyphs613* int FLAG_HAS_NONUNIFORM_ORDER = 2; // set if some glyphs are rearranged out of character visual order614* int FLAG_HAS_SPLIT_CHARACTERS = 4; // set if multiple glyphs per character615* int getDescriptionFlags(); // return an int containing the above flags616* boolean hasZeroAdvanceGlyphs();617* boolean hasNonuniformOrder();618* boolean hasSplitCharacters();619* The optimized cases in (4) correspond to values 0, 1, 3, and 7 returned by getDescriptionFlags().620*/621protected float[] createCharinfo() {622StandardGlyphVector gv = getGV();623float[] glyphinfo = null;624try {625glyphinfo = gv.getGlyphInfo();626}627catch (Exception e) {628if (DEBUG) {629System.err.println(source);630e.printStackTrace();631}632glyphinfo = new float[gv.getNumGlyphs() * numvals];633}634635int numGlyphs = gv.getNumGlyphs();636if (numGlyphs == 0) {637return glyphinfo;638}639int[] indices = gv.getGlyphCharIndices(0, numGlyphs, null);640float[] charInfo = new float[source.getLength() * numvals];641642if (DEBUG) {643System.err.println("number of glyphs: " + numGlyphs);644System.err.println("glyphinfo.len: " + glyphinfo.length);645System.err.println("indices.len: " + indices.length);646for (int i = 0; i < numGlyphs; ++i) {647System.err.println("g: " + i +648" v: " + gv.getGlyphCode(i) +649", x: " + glyphinfo[i*numvals+posx] +650", a: " + glyphinfo[i*numvals+advx] +651", n: " + indices[i]);652}653}654655int minIndex = indices[0]; // smallest index seen this cluster656int maxIndex = minIndex; // largest index seen this cluster657int cp = 0; // character position658int cc = 0;659int gp = 0; // glyph position660int gx = 0; // glyph index (visual)661int gxlimit = numGlyphs; // limit of gx, when we reach this we're done662int pdelta = numvals; // delta for incrementing positions663int xdelta = 1; // delta for incrementing indices664665boolean rtl = (source.getLayoutFlags() & 0x1) == 1;666if (rtl) {667minIndex = indices[numGlyphs - 1];668maxIndex = minIndex;669cp = charInfo.length - numvals;670gp = glyphinfo.length - numvals;671gx = numGlyphs - 1;672gxlimit = -1;673pdelta = -numvals;674xdelta = -1;675}676677/*678// to support vertical, use 'ixxxx' indices and swap horiz and vertical components679if (source.isVertical()) {680iposx = posy;681iposy = posx;682iadvx = advy;683iadvy = advx;684ivisx = visy;685ivisy = visx;686ivish = visw;687ivisw = vish;688} else {689// use standard values690}691*/692693// use intermediates to reduce array access when we need to694float cposl = 0, cposr = 0, cvisl = 0, cvist = 0, cvisr = 0, cvisb = 0;695float baseline = 0;696697while (gx != gxlimit) {698// start of new cluster699int clusterExtraGlyphs = 0;700701minIndex = indices[gx];702maxIndex = minIndex;703704cposl = glyphinfo[gp + posx];705cposr = cposl + glyphinfo[gp + advx];706cvisl = glyphinfo[gp + visx];707cvist = glyphinfo[gp + visy];708cvisr = cvisl + glyphinfo[gp + visw];709cvisb = cvist + glyphinfo[gp + vish];710711// advance to next glyph712gx += xdelta;713gp += pdelta;714715while (gx != gxlimit &&716((glyphinfo[gp + advx] == 0) ||717(indices[gx] <= maxIndex) ||718(maxIndex - minIndex > clusterExtraGlyphs))) {719720++clusterExtraGlyphs; // have an extra glyph in this cluster721if (DEBUG) {722System.err.println("gp=" +gp +" adv=" + glyphinfo[gp + advx] +723" gx="+ gx+ " i[gx]="+indices[gx] +724" clusterExtraGlyphs="+clusterExtraGlyphs);725}726727// adjust advance only if new glyph has non-zero advance728float radvx = glyphinfo[gp + advx];729if (radvx != 0) {730float rposx = glyphinfo[gp + posx];731cposl = Math.min(cposl, rposx);732cposr = Math.max(cposr, rposx + radvx);733}734735// adjust visible bounds only if new glyph has non-empty bounds736float rvisw = glyphinfo[gp + visw];737if (rvisw != 0) {738float rvisx = glyphinfo[gp + visx];739float rvisy = glyphinfo[gp + visy];740cvisl = Math.min(cvisl, rvisx);741cvist = Math.min(cvist, rvisy);742cvisr = Math.max(cvisr, rvisx + rvisw);743cvisb = Math.max(cvisb, rvisy + glyphinfo[gp + vish]);744}745746// adjust min, max index747minIndex = Math.min(minIndex, indices[gx]);748maxIndex = Math.max(maxIndex, indices[gx]);749750// get ready to examine next glyph751gx += xdelta;752gp += pdelta;753}754// done with cluster, gx and gp are set for next glyph755756if (DEBUG) {757System.err.println("minIndex = " + minIndex + ", maxIndex = " + maxIndex);758}759760// save adjustments to the base character and do common adjustments.761charInfo[cp + posx] = cposl;762charInfo[cp + posy] = baseline;763charInfo[cp + advx] = cposr - cposl;764charInfo[cp + advy] = 0;765charInfo[cp + visx] = cvisl;766charInfo[cp + visy] = cvist;767charInfo[cp + visw] = cvisr - cvisl;768charInfo[cp + vish] = cvisb - cvist;769cc++;770771/* We may have consumed multiple glyphs for this char position.772* Map those extra consumed glyphs to char positions that would follow773* up to the index prior to that which begins the next cluster.774* If we have reached the last glyph (reached gxlimit) then we need to775* map remaining unmapped chars to the same location as the last one.776*/777int tgt;778if (gx == gxlimit) {779tgt = charInfo.length / numvals;780} else {781tgt = indices[gx];782}783if (DEBUG) {784System.err.println("gx=" + gx + " gxlimit=" + gxlimit +785" charInfo.len=" + charInfo.length +786" tgt=" + tgt + " cc=" + cc + " cp=" + cp);787}788while (cc < tgt) {789if (rtl) {790// if rtl, characters to left of base, else to right. reuse cposr.791cposr = cposl;792}793cvisr -= cvisl; // reuse, convert to deltas.794cvisb -= cvist;795796cp += pdelta;797798if (cp < 0 || cp >= charInfo.length) {799if (DEBUG) {800System.err.println("Error : cp=" + cp +801" charInfo.length=" + charInfo.length);802}803break;804}805806if (DEBUG) {807System.err.println("Insert charIndex " + cc + " at pos="+cp);808}809charInfo[cp + posx] = cposr;810charInfo[cp + posy] = baseline;811charInfo[cp + advx] = 0;812charInfo[cp + advy] = 0;813charInfo[cp + visx] = cvisl;814charInfo[cp + visy] = cvist;815charInfo[cp + visw] = cvisr;816charInfo[cp + vish] = cvisb;817cc++;818}819cp += pdelta; // reset for new cluster820}821822if (DEBUG) {823char[] chars = source.getChars();824int start = source.getStart();825int length = source.getLength();826System.err.println("char info for " + length + " characters");827828for (int i = 0; i < length * numvals;) {829System.err.println(" ch: " + Integer.toHexString(chars[start + v2l(i / numvals)]) +830" x: " + charInfo[i++] +831" y: " + charInfo[i++] +832" xa: " + charInfo[i++] +833" ya: " + charInfo[i++] +834" l: " + charInfo[i++] +835" t: " + charInfo[i++] +836" w: " + charInfo[i++] +837" h: " + charInfo[i++]);838}839}840return charInfo;841}842843/**844* Map logical character index to visual character index.845* <p>846* This ignores hindi reordering. @see createCharinfo847*/848protected int l2v(int index) {849return (source.getLayoutFlags() & 0x1) == 0 ? index : source.getLength() - 1 - index;850}851852/**853* Map visual character index to logical character index.854* <p>855* This ignores hindi reordering. @see createCharinfo856*/857protected int v2l(int index) {858return (source.getLayoutFlags() & 0x1) == 0 ? index : source.getLength() - 1 - index;859}860861public TextLineComponent getSubset(int start, int limit, int dir) {862return new ExtendedTextSourceLabel(source.getSubSource(start, limit-start, dir), decorator);863}864865public String toString() {866if (true) {867return source.toString(TextSource.WITHOUT_CONTEXT);868}869StringBuilder sb = new StringBuilder();870sb.append(super.toString());871sb.append("[source:");872sb.append(source.toString(TextSource.WITHOUT_CONTEXT));873sb.append(", lb:");874sb.append(lb);875sb.append(", ab:");876sb.append(ab);877sb.append(", vb:");878sb.append(vb);879sb.append(", gv:");880sb.append(gv);881sb.append(", ci: ");882if (charinfo == null) {883sb.append("null");884} else {885sb.append(charinfo[0]);886for (int i = 1; i < charinfo.length;) {887sb.append(i % numvals == 0 ? "; " : ", ");888sb.append(charinfo[i]);889}890}891sb.append("]");892893return sb.toString();894}895896//public static ExtendedTextLabel create(TextSource source) {897// return new ExtendedTextSourceLabel(source);898//}899900public int getNumJustificationInfos() {901return getGV().getNumGlyphs();902}903904905public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit) {906// This simple implementation only uses spaces for justification.907// Since regular characters aren't justified, we don't need to deal with908// special infos for combining marks or ligature substitution glyphs.909// added character justification for kanjii only 2/22/98910911StandardGlyphVector gv = getGV();912913float[] charinfo = getCharinfo();914915float size = gv.getFont().getSize2D();916917GlyphJustificationInfo nullInfo =918new GlyphJustificationInfo(0,919false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0,920false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0);921922GlyphJustificationInfo spaceInfo =923new GlyphJustificationInfo(size,924true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, size,925true, GlyphJustificationInfo.PRIORITY_WHITESPACE, 0, size / 4f);926927GlyphJustificationInfo kanjiInfo =928new GlyphJustificationInfo(size,929true, GlyphJustificationInfo.PRIORITY_INTERCHAR, size, size,930false, GlyphJustificationInfo.PRIORITY_NONE, 0, 0);931932char[] chars = source.getChars();933int offset = source.getStart();934935// assume data is 1-1 and either all rtl or all ltr, for now936937int numGlyphs = gv.getNumGlyphs();938int minGlyph = 0;939int maxGlyph = numGlyphs;940boolean ltr = (source.getLayoutFlags() & 0x1) == 0;941if (charStart != 0 || charLimit != source.getLength()) {942if (ltr) {943minGlyph = charStart;944maxGlyph = charLimit;945} else {946minGlyph = numGlyphs - charLimit;947maxGlyph = numGlyphs - charStart;948}949}950951for (int i = 0; i < numGlyphs; ++i) {952GlyphJustificationInfo info = null;953if (i >= minGlyph && i < maxGlyph) {954if (charinfo[i * numvals + advx] == 0) { // combining marks don't justify955info = nullInfo;956} else {957int ci = v2l(i); // 1-1 assumption again958char c = chars[offset + ci];959if (Character.isWhitespace(c)) {960info = spaceInfo;961// CJK, Hangul, CJK Compatibility areas962} else if (c >= 0x4e00 &&963(c < 0xa000) ||964(c >= 0xac00 && c < 0xd7b0) ||965(c >= 0xf900 && c < 0xfb00)) {966info = kanjiInfo;967} else {968info = nullInfo;969}970}971}972infos[infoStart + i] = info;973}974}975976public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags) {977978// when we justify, we need to adjust the charinfo since spaces979// change their advances. preserve the existing charinfo.980981float[] newCharinfo = getCharinfo().clone();982983// we only push spaces, so never need to rejustify984flags[0] = false;985986// preserve the existing gv.987988StandardGlyphVector newgv = (StandardGlyphVector)getGV().clone();989float[] newPositions = newgv.getGlyphPositions(null);990int numGlyphs = newgv.getNumGlyphs();991992/*993System.out.println("oldgv: " + getGV() + ", newgv: " + newgv);994System.out.println("newpositions: " + newPositions);995for (int i = 0; i < newPositions.length; i += 2) {996System.out.println("[" + (i/2) + "] " + newPositions[i] + ", " + newPositions[i+1]);997}998999System.out.println("deltas: " + deltas + " start: " + deltaStart);1000for (int i = deltaStart; i < deltaStart + numGlyphs; i += 2) {1001System.out.println("[" + (i/2) + "] " + deltas[i] + ", " + deltas[i+1]);1002}1003*/10041005char[] chars = source.getChars();1006int offset = source.getStart();10071008// accumulate the deltas to adjust positions and advances.1009// handle whitespace by modifying advance,1010// handle everything else by modifying position before and after10111012float deltaPos = 0;1013for (int i = 0; i < numGlyphs; ++i) {1014if (Character.isWhitespace(chars[offset + v2l(i)])) {1015newPositions[i*2] += deltaPos;10161017float deltaAdv = deltas[deltaStart + i*2] + deltas[deltaStart + i*2 + 1];10181019newCharinfo[i * numvals + posx] += deltaPos;1020newCharinfo[i * numvals + visx] += deltaPos;1021newCharinfo[i * numvals + advx] += deltaAdv;10221023deltaPos += deltaAdv;1024} else {1025deltaPos += deltas[deltaStart + i*2];10261027newPositions[i*2] += deltaPos;1028newCharinfo[i * numvals + posx] += deltaPos;1029newCharinfo[i * numvals + visx] += deltaPos;10301031deltaPos += deltas[deltaStart + i*2 + 1];1032}1033}1034newPositions[numGlyphs * 2] += deltaPos;10351036newgv.setGlyphPositions(newPositions);10371038/*1039newPositions = newgv.getGlyphPositions(null);1040System.out.println(">> newpositions: " + newPositions);1041for (int i = 0; i < newPositions.length; i += 2) {1042System.out.println("[" + (i/2) + "] " + newPositions[i] + ", " + newPositions[i+1]);1043}1044*/10451046ExtendedTextSourceLabel result = new ExtendedTextSourceLabel(source, decorator);1047result.gv = newgv;1048result.charinfo = newCharinfo;10491050return result;1051}1052}105310541055