Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/font/GlyphLayout.java
38829 views
1
/*
2
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
/*
27
*
28
* (C) Copyright IBM Corp. 1999-2003 - All Rights Reserved
29
*
30
* The original version of this source code and documentation is
31
* copyrighted and owned by IBM. These materials are provided
32
* under terms of a License Agreement between IBM and Sun.
33
* This technology is protected by multiple US and International
34
* patents. This notice and attribution to IBM may not be removed.
35
*/
36
37
/*
38
* GlyphLayout is used to process a run of text into a run of run of
39
* glyphs, optionally with position and char mapping info.
40
*
41
* The text has already been processed for numeric shaping and bidi.
42
* The run of text that layout works on has a single bidi level. It
43
* also has a single font/style. Some operations need context to work
44
* on (shaping, script resolution) so context for the text run text is
45
* provided. It is assumed that the text array contains sufficient
46
* context, and the offset and count delimit the portion of the text
47
* that needs to actually be processed.
48
*
49
* The font might be a composite font. Layout generally requires
50
* tables from a single physical font to operate, and so it must
51
* resolve the 'single' font run into runs of physical fonts.
52
*
53
* Some characters are supported by several fonts of a composite, and
54
* in order to properly emulate the glyph substitution behavior of a
55
* single physical font, these characters might need to be mapped to
56
* different physical fonts. The script code that is assigned
57
* characters normally considered 'common script' can be used to
58
* resolve which physical font to use for these characters. The input
59
* to the char to glyph mapper (which assigns physical fonts as it
60
* processes the glyphs) should include the script code, and the
61
* mapper should operate on runs of a single script.
62
*
63
* To perform layout, call get() to get a new (or reuse an old)
64
* GlyphLayout, call layout on it, then call done(GlyphLayout) when
65
* finished. There's no particular problem if you don't call done,
66
* but it assists in reuse of the GlyphLayout.
67
*/
68
69
package sun.font;
70
71
import java.lang.ref.SoftReference;
72
import java.awt.Font;
73
import java.awt.font.FontRenderContext;
74
import java.awt.font.GlyphVector;
75
import java.awt.geom.AffineTransform;
76
import java.awt.geom.NoninvertibleTransformException;
77
import java.awt.geom.Point2D;
78
import java.util.ArrayList;
79
import java.util.concurrent.ConcurrentHashMap;
80
81
import static java.lang.Character.*;
82
83
public final class GlyphLayout {
84
// data for glyph vector
85
private GVData _gvdata;
86
87
// cached glyph layout data for reuse
88
private static volatile GlyphLayout cache; // reusable
89
90
private LayoutEngineFactory _lef; // set when get is called, unset when done is called
91
private TextRecord _textRecord; // the text we're working on, used by iterators
92
private ScriptRun _scriptRuns; // iterator over script runs
93
private FontRunIterator _fontRuns; // iterator over physical fonts in a composite
94
private int _ercount;
95
private ArrayList _erecords;
96
private Point2D.Float _pt;
97
private FontStrikeDesc _sd;
98
private float[] _mat;
99
private int _typo_flags;
100
private int _offset;
101
102
public static final class LayoutEngineKey {
103
private Font2D font;
104
private int script;
105
private int lang;
106
107
LayoutEngineKey() {
108
}
109
110
LayoutEngineKey(Font2D font, int script, int lang) {
111
init(font, script, lang);
112
}
113
114
void init(Font2D font, int script, int lang) {
115
this.font = font;
116
this.script = script;
117
this.lang = lang;
118
}
119
120
LayoutEngineKey copy() {
121
return new LayoutEngineKey(font, script, lang);
122
}
123
124
Font2D font() {
125
return font;
126
}
127
128
int script() {
129
return script;
130
}
131
132
int lang() {
133
return lang;
134
}
135
136
public boolean equals(Object rhs) {
137
if (this == rhs) return true;
138
if (rhs == null) return false;
139
try {
140
LayoutEngineKey that = (LayoutEngineKey)rhs;
141
return this.script == that.script &&
142
this.lang == that.lang &&
143
this.font.equals(that.font);
144
}
145
catch (ClassCastException e) {
146
return false;
147
}
148
}
149
150
public int hashCode() {
151
return script ^ lang ^ font.hashCode();
152
}
153
}
154
155
public static interface LayoutEngineFactory {
156
/**
157
* Given a font, script, and language, determine a layout engine to use.
158
*/
159
public LayoutEngine getEngine(Font2D font, int script, int lang);
160
161
/**
162
* Given a key, determine a layout engine to use.
163
*/
164
public LayoutEngine getEngine(LayoutEngineKey key);
165
}
166
167
public static interface LayoutEngine {
168
/**
169
* Given a strike descriptor, text, rtl flag, and starting point, append information about
170
* glyphs, positions, and character indices to the glyphvector data, and advance the point.
171
*
172
* If the GVData does not have room for the glyphs, throws an IndexOutOfBoundsException and
173
* leave pt and the gvdata unchanged.
174
*/
175
public void layout(FontStrikeDesc sd, float[] mat, int gmask,
176
int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data);
177
}
178
179
/**
180
* Return a new instance of GlyphLayout, using the provided layout engine factory.
181
* If null, the system layout engine factory will be used.
182
*/
183
public static GlyphLayout get(LayoutEngineFactory lef) {
184
if (lef == null) {
185
lef = SunLayoutEngine.instance();
186
}
187
GlyphLayout result = null;
188
synchronized(GlyphLayout.class) {
189
if (cache != null) {
190
result = cache;
191
cache = null;
192
}
193
}
194
if (result == null) {
195
result = new GlyphLayout();
196
}
197
result._lef = lef;
198
return result;
199
}
200
201
/**
202
* Return the old instance of GlyphLayout when you are done. This enables reuse
203
* of GlyphLayout objects.
204
*/
205
public static void done(GlyphLayout gl) {
206
gl._lef = null;
207
cache = gl; // object reference assignment is thread safe, it says here...
208
}
209
210
private static final class SDCache {
211
public Font key_font;
212
public FontRenderContext key_frc;
213
214
public AffineTransform dtx;
215
public AffineTransform invdtx;
216
public AffineTransform gtx;
217
public Point2D.Float delta;
218
public FontStrikeDesc sd;
219
220
private SDCache(Font font, FontRenderContext frc) {
221
key_font = font;
222
key_frc = frc;
223
224
// !!! add getVectorTransform and hasVectorTransform to frc? then
225
// we could just skip this work...
226
227
dtx = frc.getTransform();
228
dtx.setTransform(dtx.getScaleX(), dtx.getShearY(),
229
dtx.getShearX(), dtx.getScaleY(),
230
0, 0);
231
if (!dtx.isIdentity()) {
232
try {
233
invdtx = dtx.createInverse();
234
}
235
catch (NoninvertibleTransformException e) {
236
throw new InternalError(e);
237
}
238
}
239
240
float ptSize = font.getSize2D();
241
if (font.isTransformed()) {
242
gtx = font.getTransform();
243
gtx.scale(ptSize, ptSize);
244
delta = new Point2D.Float((float)gtx.getTranslateX(),
245
(float)gtx.getTranslateY());
246
gtx.setTransform(gtx.getScaleX(), gtx.getShearY(),
247
gtx.getShearX(), gtx.getScaleY(),
248
0, 0);
249
gtx.preConcatenate(dtx);
250
} else {
251
delta = ZERO_DELTA;
252
gtx = new AffineTransform(dtx);
253
gtx.scale(ptSize, ptSize);
254
}
255
256
/* Similar logic to that used in SunGraphics2D.checkFontInfo().
257
* Whether a grey (AA) strike is needed is size dependent if
258
* AA mode is 'gasp'.
259
*/
260
int aa =
261
FontStrikeDesc.getAAHintIntVal(frc.getAntiAliasingHint(),
262
FontUtilities.getFont2D(font),
263
(int)Math.abs(ptSize));
264
int fm = FontStrikeDesc.getFMHintIntVal
265
(frc.getFractionalMetricsHint());
266
sd = new FontStrikeDesc(dtx, gtx, font.getStyle(), aa, fm);
267
}
268
269
private static final Point2D.Float ZERO_DELTA = new Point2D.Float();
270
271
private static
272
SoftReference<ConcurrentHashMap<SDKey, SDCache>> cacheRef;
273
274
private static final class SDKey {
275
private final Font font;
276
private final FontRenderContext frc;
277
private final int hash;
278
279
SDKey(Font font, FontRenderContext frc) {
280
this.font = font;
281
this.frc = frc;
282
this.hash = font.hashCode() ^ frc.hashCode();
283
}
284
285
public int hashCode() {
286
return hash;
287
}
288
289
public boolean equals(Object o) {
290
try {
291
SDKey rhs = (SDKey)o;
292
return
293
hash == rhs.hash &&
294
font.equals(rhs.font) &&
295
frc.equals(rhs.frc);
296
}
297
catch (ClassCastException e) {
298
}
299
return false;
300
}
301
}
302
303
public static SDCache get(Font font, FontRenderContext frc) {
304
305
// It is possible a translation component will be in the FRC.
306
// It doesn't affect us except adversely as we would consider
307
// FRC's which are really the same to be different. If we
308
// detect a translation component, then we need to exclude it
309
// by creating a new transform which excludes the translation.
310
if (frc.isTransformed()) {
311
AffineTransform transform = frc.getTransform();
312
if (transform.getTranslateX() != 0 ||
313
transform.getTranslateY() != 0) {
314
transform = new AffineTransform(transform.getScaleX(),
315
transform.getShearY(),
316
transform.getShearX(),
317
transform.getScaleY(),
318
0, 0);
319
frc = new FontRenderContext(transform,
320
frc.getAntiAliasingHint(),
321
frc.getFractionalMetricsHint()
322
);
323
}
324
}
325
326
SDKey key = new SDKey(font, frc); // garbage, yuck...
327
ConcurrentHashMap<SDKey, SDCache> cache = null;
328
SDCache res = null;
329
if (cacheRef != null) {
330
cache = cacheRef.get();
331
if (cache != null) {
332
res = cache.get(key);
333
}
334
}
335
if (res == null) {
336
res = new SDCache(font, frc);
337
if (cache == null) {
338
cache = new ConcurrentHashMap<SDKey, SDCache>(10);
339
cacheRef = new
340
SoftReference<ConcurrentHashMap<SDKey, SDCache>>(cache);
341
} else if (cache.size() >= 512) {
342
cache.clear();
343
}
344
cache.put(key, res);
345
}
346
return res;
347
}
348
}
349
350
/**
351
* Create a glyph vector.
352
* @param font the font to use
353
* @param frc the font render context
354
* @param text the text, including optional context before start and after start + count
355
* @param offset the start of the text to lay out
356
* @param count the length of the text to lay out
357
* @param flags bidi and context flags {@see #java.awt.Font}
358
* @param result a StandardGlyphVector to modify, can be null
359
* @return the layed out glyphvector, if result was passed in, it is returned
360
*/
361
public StandardGlyphVector layout(Font font, FontRenderContext frc,
362
char[] text, int offset, int count,
363
int flags, StandardGlyphVector result)
364
{
365
if (text == null || offset < 0 || count < 0 || (count > text.length - offset)) {
366
throw new IllegalArgumentException();
367
}
368
369
init(count);
370
371
// need to set after init
372
// go through the back door for this
373
if (font.hasLayoutAttributes()) {
374
AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();
375
if (values.getKerning() != 0) _typo_flags |= 0x1;
376
if (values.getLigatures() != 0) _typo_flags |= 0x2;
377
}
378
379
_offset = offset;
380
381
// use cache now - can we use the strike cache for this?
382
383
SDCache txinfo = SDCache.get(font, frc);
384
_mat[0] = (float)txinfo.gtx.getScaleX();
385
_mat[1] = (float)txinfo.gtx.getShearY();
386
_mat[2] = (float)txinfo.gtx.getShearX();
387
_mat[3] = (float)txinfo.gtx.getScaleY();
388
_pt.setLocation(txinfo.delta);
389
390
int lim = offset + count;
391
392
int min = 0;
393
int max = text.length;
394
if (flags != 0) {
395
if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) {
396
_typo_flags |= 0x80000000; // RTL
397
}
398
399
if ((flags & Font.LAYOUT_NO_START_CONTEXT) != 0) {
400
min = offset;
401
}
402
403
if ((flags & Font.LAYOUT_NO_LIMIT_CONTEXT) != 0) {
404
max = lim;
405
}
406
}
407
408
int lang = -1; // default for now
409
410
Font2D font2D = FontUtilities.getFont2D(font);
411
if (font2D instanceof FontSubstitution) {
412
font2D = ((FontSubstitution)font2D).getCompositeFont2D();
413
}
414
415
_textRecord.init(text, offset, lim, min, max);
416
int start = offset;
417
if (font2D instanceof CompositeFont) {
418
_scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars
419
_fontRuns.init((CompositeFont)font2D, text, offset, lim);
420
while (_scriptRuns.next()) {
421
int limit = _scriptRuns.getScriptLimit();
422
int script = _scriptRuns.getScriptCode();
423
while (_fontRuns.next(script, limit)) {
424
Font2D pfont = _fontRuns.getFont();
425
/* layout can't deal with NativeFont instances. The
426
* native font is assumed to know of a suitable non-native
427
* substitute font. This currently works because
428
* its consistent with the way NativeFonts delegate
429
* in other cases too.
430
*/
431
if (pfont instanceof NativeFont) {
432
pfont = ((NativeFont)pfont).getDelegateFont();
433
}
434
int gmask = _fontRuns.getGlyphMask();
435
int pos = _fontRuns.getPos();
436
nextEngineRecord(start, pos, script, lang, pfont, gmask);
437
start = pos;
438
}
439
}
440
} else {
441
_scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars
442
while (_scriptRuns.next()) {
443
int limit = _scriptRuns.getScriptLimit();
444
int script = _scriptRuns.getScriptCode();
445
nextEngineRecord(start, limit, script, lang, font2D, 0);
446
start = limit;
447
}
448
}
449
450
int ix = 0;
451
int stop = _ercount;
452
int dir = 1;
453
454
if (_typo_flags < 0) { // RTL
455
ix = stop - 1;
456
stop = -1;
457
dir = -1;
458
}
459
460
// _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics());
461
_sd = txinfo.sd;
462
for (;ix != stop; ix += dir) {
463
EngineRecord er = (EngineRecord)_erecords.get(ix);
464
for (;;) {
465
try {
466
er.layout();
467
break;
468
}
469
catch (IndexOutOfBoundsException e) {
470
if (_gvdata._count >=0) {
471
_gvdata.grow();
472
}
473
}
474
}
475
// Break out of the outer for loop if layout fails.
476
if (_gvdata._count < 0) {
477
break;
478
}
479
}
480
481
// if (txinfo.invdtx != null) {
482
// _gvdata.adjustPositions(txinfo.invdtx);
483
// }
484
485
// If layout fails (negative glyph count) create an un-laid out GV instead.
486
// ie default positions. This will be a lot better than the alternative of
487
// a complete blank layout.
488
StandardGlyphVector gv;
489
if (_gvdata._count < 0) {
490
gv = new StandardGlyphVector(font, text, offset, count, frc);
491
if (FontUtilities.debugFonts()) {
492
FontUtilities.getLogger().warning("OpenType layout failed on font: " +
493
font);
494
}
495
} else {
496
gv = _gvdata.createGlyphVector(font, frc, result);
497
}
498
// System.err.println("Layout returns: " + gv);
499
return gv;
500
}
501
502
//
503
// private methods
504
//
505
506
private GlyphLayout() {
507
this._gvdata = new GVData();
508
this._textRecord = new TextRecord();
509
this._scriptRuns = new ScriptRun();
510
this._fontRuns = new FontRunIterator();
511
this._erecords = new ArrayList(10);
512
this._pt = new Point2D.Float();
513
this._sd = new FontStrikeDesc();
514
this._mat = new float[4];
515
}
516
517
private void init(int capacity) {
518
this._typo_flags = 0;
519
this._ercount = 0;
520
this._gvdata.init(capacity);
521
}
522
523
private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {
524
EngineRecord er = null;
525
if (_ercount == _erecords.size()) {
526
er = new EngineRecord();
527
_erecords.add(er);
528
} else {
529
er = (EngineRecord)_erecords.get(_ercount);
530
}
531
er.init(start, limit, font, script, lang, gmask);
532
++_ercount;
533
}
534
535
/**
536
* Storage for layout to build glyph vector data, then generate a real GlyphVector
537
*/
538
public static final class GVData {
539
public int _count; // number of glyphs, >= number of chars
540
public int _flags;
541
public int[] _glyphs;
542
public float[] _positions;
543
public int[] _indices;
544
545
private static final int UNINITIALIZED_FLAGS = -1;
546
547
public void init(int size) {
548
_count = 0;
549
_flags = UNINITIALIZED_FLAGS;
550
551
if (_glyphs == null || _glyphs.length < size) {
552
if (size < 20) {
553
size = 20;
554
}
555
_glyphs = new int[size];
556
_positions = new float[size * 2 + 2];
557
_indices = new int[size];
558
}
559
}
560
561
public void grow() {
562
grow(_glyphs.length / 4); // always grows because min length is 20
563
}
564
565
public void grow(int delta) {
566
int size = _glyphs.length + delta;
567
int[] nglyphs = new int[size];
568
System.arraycopy(_glyphs, 0, nglyphs, 0, _count);
569
_glyphs = nglyphs;
570
571
float[] npositions = new float[size * 2 + 2];
572
System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);
573
_positions = npositions;
574
575
int[] nindices = new int[size];
576
System.arraycopy(_indices, 0, nindices, 0, _count);
577
_indices = nindices;
578
}
579
580
public void adjustPositions(AffineTransform invdtx) {
581
invdtx.transform(_positions, 0, _positions, 0, _count);
582
}
583
584
public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {
585
586
// !!! default initialization until we let layout engines do it
587
if (_flags == UNINITIALIZED_FLAGS) {
588
_flags = 0;
589
590
if (_count > 1) { // if only 1 glyph assume LTR
591
boolean ltr = true;
592
boolean rtl = true;
593
594
int rtlix = _count; // rtl index
595
for (int i = 0; i < _count && (ltr || rtl); ++i) {
596
int cx = _indices[i];
597
598
ltr = ltr && (cx == i);
599
rtl = rtl && (cx == --rtlix);
600
}
601
602
if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;
603
if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;
604
}
605
606
// !!! layout engines need to tell us whether they performed
607
// position adjustments. currently they don't tell us, so
608
// we must assume they did
609
_flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;
610
}
611
612
int[] glyphs = new int[_count];
613
System.arraycopy(_glyphs, 0, glyphs, 0, _count);
614
615
float[] positions = null;
616
if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
617
positions = new float[_count * 2 + 2];
618
System.arraycopy(_positions, 0, positions, 0, positions.length);
619
}
620
621
int[] indices = null;
622
if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {
623
indices = new int[_count];
624
System.arraycopy(_indices, 0, indices, 0, _count);
625
}
626
627
if (result == null) {
628
result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);
629
} else {
630
result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);
631
}
632
633
return result;
634
}
635
}
636
637
/**
638
* Utility class to keep track of script runs, which may have to be reordered rtl when we're
639
* finished.
640
*/
641
private final class EngineRecord {
642
private int start;
643
private int limit;
644
private int gmask;
645
private int eflags;
646
private LayoutEngineKey key;
647
private LayoutEngine engine;
648
649
EngineRecord() {
650
key = new LayoutEngineKey();
651
}
652
653
void init(int start, int limit, Font2D font, int script, int lang, int gmask) {
654
this.start = start;
655
this.limit = limit;
656
this.gmask = gmask;
657
this.key.init(font, script, lang);
658
this.eflags = 0;
659
660
// only request canonical substitution if we have combining marks
661
for (int i = start; i < limit; ++i) {
662
int ch = _textRecord.text[i];
663
if (isHighSurrogate((char)ch) &&
664
i < limit - 1 &&
665
isLowSurrogate(_textRecord.text[i+1])) {
666
// rare case
667
ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc
668
}
669
int gc = getType(ch);
670
if (gc == NON_SPACING_MARK ||
671
gc == ENCLOSING_MARK ||
672
gc == COMBINING_SPACING_MARK) { // could do range test also
673
674
this.eflags = 0x4;
675
break;
676
}
677
}
678
679
this.engine = _lef.getEngine(key); // flags?
680
}
681
682
void layout() {
683
_textRecord.start = start;
684
_textRecord.limit = limit;
685
engine.layout(_sd, _mat, gmask, start - _offset, _textRecord,
686
_typo_flags | eflags, _pt, _gvdata);
687
}
688
}
689
}
690
691