@@ -63,8 +63,160 @@ public String toString () {
63
63
long layout , context , attrList , selAttrList ;
64
64
int [] invalidOffsets ;
65
65
int verticalIndentInPoints ;
66
+ MetricsAdapter metricsAdapter = new MetricsAdapter ();
66
67
static final char LTR_MARK = '\u200E' , RTL_MARK = '\u200F' , ZWS = '\u200B' , ZWNBS = '\uFEFF' ;
67
68
69
+ /**
70
+ * Adapts necessary Pango APIs to enforce fixed line metrics (when set)
71
+ */
72
+ private static class MetricsAdapter {
73
+ private FontMetrics lineMetricsInPixels ;
74
+
75
+ /**
76
+ * Calculates Y offset from line metrics configured in
77
+ * {@link #lineMetricsInPixels} to real text position for painting.
78
+ */
79
+ private int wantToRealInPango (PangoRectangle realMetrics ) {
80
+ int wantHeightInPixels = lineMetricsInPixels .getHeight ();
81
+ int realHeightInPixels = OS .PANGO_PIXELS (realMetrics .height );
82
+ if (realHeightInPixels == wantHeightInPixels ) {
83
+ return 0 ;
84
+ }
85
+
86
+ // The idea is to preserve baseline location, this looks best.
87
+ // This is the behavior documented in `TextLayout#setFixedLineMetrics()`.
88
+ int wantAboveInPango = OS .PANGO_SCALE * lineMetricsInPixels .getAscent ();
89
+ int realAboveInPango = -realMetrics .y ;
90
+ return wantAboveInPango - realAboveInPango ;
91
+ }
92
+
93
+ private int wantToRealInPango (long line ) {
94
+ PangoRectangle rect = new PangoRectangle ();
95
+ // Pango caches result, so the API is very cheap to call multiple times
96
+ OS .pango_layout_line_get_extents (line , null , rect );
97
+ return wantToRealInPango (rect );
98
+ }
99
+
100
+ public boolean isFixedMetrics () {
101
+ return (lineMetricsInPixels != null );
102
+ }
103
+
104
+ public FontMetrics getFixedLineMetrics (Device device ) {
105
+ if (lineMetricsInPixels == null ) {
106
+ return null ;
107
+ }
108
+
109
+ FontMetrics result = new FontMetrics ();
110
+ result .ascentInPoints = DPIUtil .autoScaleDown (device , lineMetricsInPixels .ascentInPoints );
111
+ result .descentInPoints = DPIUtil .autoScaleDown (device , lineMetricsInPixels .descentInPoints );
112
+ result .averageCharWidthInPoints = DPIUtil .autoScaleDown (device , lineMetricsInPixels .averageCharWidthInPoints );
113
+
114
+ return result ;
115
+ }
116
+
117
+ public void setFixedLineMetrics (Device device , FontMetrics metrics ) {
118
+ if (metrics == null ) {
119
+ lineMetricsInPixels = null ;
120
+ return ;
121
+ }
122
+
123
+ FontMetrics result = new FontMetrics ();
124
+ result .ascentInPoints = DPIUtil .autoScaleUp (device , metrics .ascentInPoints );
125
+ result .descentInPoints = DPIUtil .autoScaleUp (device , metrics .descentInPoints );
126
+ result .averageCharWidthInPoints = DPIUtil .autoScaleUp (device , metrics .averageCharWidthInPoints );
127
+
128
+ lineMetricsInPixels = result ;
129
+ }
130
+
131
+ private void validateLayout (long layout ) {
132
+ // Pango caches result, so the API is very cheap to call multiple times
133
+ if (OS .pango_layout_get_line_count (layout ) > 1 ) {
134
+ // Multi-line layouts (including word wrapping) are not yet supported.
135
+ // Note that `StyledText` uses separate `TextLayout` for every line.
136
+ SWT .error (SWT .ERROR_INVALID_ARGUMENT );
137
+ }
138
+ }
139
+
140
+ public long gdk_pango_layout_get_clip_region (long layout , int x_origin , int y_origin , int [] index_ranges , int n_ranges ) {
141
+ // In order to get proper text clip, adjust Y in the same way
142
+ // as in pango_cairo_show_layout()
143
+ int yAdjustInPixels = 0 ;
144
+ if (isFixedMetrics ()) {
145
+ validateLayout (layout );
146
+ long line0 = OS .pango_layout_get_line (layout , 0 );
147
+ yAdjustInPixels = OS .PANGO_PIXELS (wantToRealInPango (line0 ));
148
+ }
149
+
150
+ long rgn = GDK .gdk_pango_layout_get_clip_region (layout , x_origin , y_origin + yAdjustInPixels , index_ranges , n_ranges );
151
+
152
+ // Adjust region via intersecting with desired region
153
+ if (isFixedMetrics ()) {
154
+ cairo_rectangle_int_t wantRect = new cairo_rectangle_int_t ();
155
+ // Use real x,width with desired y,height
156
+ Cairo .cairo_region_get_extents (rgn , wantRect );
157
+ wantRect .y = y_origin ;
158
+ wantRect .height = lineMetricsInPixels .getHeight ();
159
+
160
+ long limitRgn = Cairo .cairo_region_create_rectangle (wantRect );
161
+ Cairo .cairo_region_intersect (rgn , limitRgn );
162
+ Cairo .cairo_region_destroy (limitRgn );
163
+ }
164
+
165
+ return rgn ;
166
+ }
167
+
168
+ public void pango_cairo_show_layout (long cairo , long layout , double x , double y ) {
169
+ int yAdjustInPixels = 0 ;
170
+ if (isFixedMetrics ()) {
171
+ validateLayout (layout );
172
+ long line0 = OS .pango_layout_get_line (layout , 0 );
173
+ yAdjustInPixels = OS .PANGO_PIXELS (wantToRealInPango (line0 ));
174
+ }
175
+
176
+ Cairo .cairo_move_to (cairo , x , y + yAdjustInPixels );
177
+ OS .pango_cairo_show_layout (cairo , layout );
178
+ }
179
+
180
+ public void pango_layout_get_size (long layout , int [] width , int [] height ) {
181
+ OS .pango_layout_get_size (layout , width , height );
182
+
183
+ if (isFixedMetrics ()) {
184
+ validateLayout (layout );
185
+ height [0 ] = lineMetricsInPixels .getHeight ();
186
+ }
187
+ }
188
+
189
+ public void pango_layout_iter_get_line_extents (long iter , PangoRectangle ink_rect , PangoRectangle logical_rect ) {
190
+ OS .pango_layout_iter_get_line_extents (iter , ink_rect , logical_rect );
191
+
192
+ if (isFixedMetrics ()) {
193
+ if (ink_rect != null ) {
194
+ // SWT doesn't use that, so I didn't implement
195
+ SWT .error (SWT .ERROR_INVALID_ARGUMENT );
196
+ }
197
+
198
+ if (logical_rect != null ) {
199
+ logical_rect .height = OS .PANGO_SCALE * lineMetricsInPixels .getHeight ();
200
+ }
201
+ }
202
+ }
203
+
204
+ public void pango_layout_line_get_extents (long line , PangoRectangle ink_rect , PangoRectangle logical_rect ) {
205
+ OS .pango_layout_line_get_extents (line , ink_rect , logical_rect );
206
+
207
+ if (isFixedMetrics ()) {
208
+ if (ink_rect != null ) {
209
+ // SWT doesn't use that, so I didn't implement
210
+ SWT .error (SWT .ERROR_INVALID_ARGUMENT );
211
+ }
212
+
213
+ if (logical_rect != null ) {
214
+ logical_rect .height = OS .PANGO_SCALE * lineMetricsInPixels .getHeight ();
215
+ }
216
+ }
217
+ }
218
+ }
219
+
68
220
/**
69
221
* Constructs a new instance of this class on the given device.
70
222
* <p>
@@ -137,7 +289,12 @@ void computeRuns () {
137
289
int nSegments = segementsLength - text .length ();
138
290
int offsetCount = nSegments ;
139
291
int [] lineOffsets = null ;
140
- if ((ascentInPoints != -1 || descentInPoints != -1 ) && segementsLength > 0 ) {
292
+
293
+ // Set minimum line ascent/descent. Feature in Pango: glyphs affected
294
+ // by `pango_attr_shape_new()` become invisible. Workaround: insert
295
+ // additional control characters and shape these instead.
296
+ boolean useMinAscentDescent = !metricsAdapter .isFixedMetrics () && (ascentInPoints != -1 || descentInPoints != -1 );
297
+ if (useMinAscentDescent && segementsLength > 0 ) {
141
298
PangoRectangle rect = new PangoRectangle ();
142
299
if (ascentInPoints != -1 ) rect .y = -(DPIUtil .autoScaleUp (getDevice (), ascentInPoints ) * OS .PANGO_SCALE );
143
300
rect .height = DPIUtil .autoScaleUp (getDevice (), (Math .max (0 , ascentInPoints ) + Math .max (0 , descentInPoints ))) * OS .PANGO_SCALE ;
@@ -502,7 +659,7 @@ void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Col
502
659
int lineIndex = 0 ;
503
660
do {
504
661
int lineEnd ;
505
- OS .pango_layout_iter_get_line_extents (iter , null , rect );
662
+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , rect );
506
663
if (OS .pango_layout_iter_next_line (iter )) {
507
664
int bytePos = OS .pango_layout_iter_get_index (iter );
508
665
lineEnd = (int )OS .g_utf16_pointer_to_offset (ptr , ptr + bytePos );
@@ -547,8 +704,7 @@ void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Col
547
704
Cairo .cairo_scale (cairo , -1 , 1 );
548
705
Cairo .cairo_translate (cairo , -2 * x - width (), 0 );
549
706
}
550
- Cairo .cairo_move_to (cairo , x , y );
551
- OS .pango_cairo_show_layout (cairo , layout );
707
+ metricsAdapter .pango_cairo_show_layout (cairo , layout , x , y );
552
708
drawBorder (gc , x , y , null );
553
709
if ((data .style & SWT .MIRRORED ) != 0 ) {
554
710
Cairo .cairo_restore (cairo );
@@ -601,12 +757,11 @@ void drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelectio
601
757
long cairo = data .cairo ;
602
758
Cairo .cairo_save (cairo );
603
759
if (!fullSelection ) {
604
- Cairo .cairo_move_to (cairo , x , y );
605
- OS .pango_cairo_show_layout (cairo , layout );
760
+ metricsAdapter .pango_cairo_show_layout (cairo , layout , x , y );
606
761
drawBorder (gc , x , y , null );
607
762
}
608
763
int [] ranges = new int []{start , end };
609
- long rgn = GDK .gdk_pango_layout_get_clip_region (layout , x , y , ranges , ranges .length / 2 );
764
+ long rgn = metricsAdapter .gdk_pango_layout_get_clip_region (layout , x , y , ranges , ranges .length / 2 );
610
765
if (rgn != 0 ) {
611
766
GDK .gdk_cairo_region (cairo , rgn );
612
767
Cairo .cairo_clip (cairo );
@@ -615,9 +770,8 @@ void drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelectio
615
770
Cairo .cairo_region_destroy (rgn );
616
771
}
617
772
Cairo .cairo_set_source_rgba (cairo , fg .red , fg .green , fg .blue , fg .alpha );
618
- Cairo .cairo_move_to (cairo , x , y );
619
773
OS .pango_layout_set_attributes (layout , selAttrList );
620
- OS .pango_cairo_show_layout (cairo , layout );
774
+ metricsAdapter .pango_cairo_show_layout (cairo , layout , x , y );
621
775
OS .pango_layout_set_attributes (layout , attrList );
622
776
drawBorder (gc , x , y , fg );
623
777
Cairo .cairo_restore (cairo );
@@ -643,7 +797,7 @@ void drawBorder(GC gc, int x, int y, GdkRGBA selectionColor) {
643
797
int byteStart = (int )(OS .g_utf16_offset_to_pointer (ptr , start ) - ptr );
644
798
int byteEnd = (int )(OS .g_utf16_offset_to_pointer (ptr , end + 1 ) - ptr );
645
799
int [] ranges = new int []{byteStart , byteEnd };
646
- long rgn = GDK .gdk_pango_layout_get_clip_region (layout , x , y , ranges , ranges .length / 2 );
800
+ long rgn = metricsAdapter .gdk_pango_layout_get_clip_region (layout , x , y , ranges , ranges .length / 2 );
647
801
if (rgn != 0 ) {
648
802
int [] nRects = new int [1 ];
649
803
long [] rects = new long [1 ];
@@ -761,7 +915,7 @@ Rectangle getBoundsInPixels(int spacingInPixels) {
761
915
checkLayout ();
762
916
computeRuns ();
763
917
int [] w = new int [1 ], h = new int [1 ];
764
- OS .pango_layout_get_size (layout , w , h );
918
+ metricsAdapter .pango_layout_get_size (layout , w , h );
765
919
int wrapWidth = OS .pango_layout_get_width (layout );
766
920
w [0 ] = wrapWidth != -1 ? wrapWidth : w [0 ] + OS .pango_layout_get_indent (layout );
767
921
int width = OS .PANGO_PIXELS (w [0 ]);
@@ -809,7 +963,7 @@ Rectangle getBoundsInPixels(int start, int end) {
809
963
byteStart = Math .min (byteStart , strlen );
810
964
byteEnd = Math .min (byteEnd , strlen );
811
965
int [] ranges = new int []{byteStart , byteEnd };
812
- long clipRegion = GDK .gdk_pango_layout_get_clip_region (layout , 0 , 0 , ranges , 1 );
966
+ long clipRegion = metricsAdapter .gdk_pango_layout_get_clip_region (layout , 0 , 0 , ranges , 1 );
813
967
if (clipRegion == 0 ) return new Rectangle (0 , 0 , 0 , 0 );
814
968
cairo_rectangle_int_t rect = new cairo_rectangle_int_t ();
815
969
@@ -825,7 +979,7 @@ Rectangle getBoundsInPixels(int start, int end) {
825
979
if (linesRegion == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
826
980
int lineEnd = 0 ;
827
981
do {
828
- OS .pango_layout_iter_get_line_extents (iter , null , pangoRect );
982
+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , pangoRect );
829
983
if (OS .pango_layout_iter_next_line (iter )) {
830
984
lineEnd = OS .pango_layout_iter_get_index (iter ) - 1 ;
831
985
} else {
@@ -996,7 +1150,7 @@ Rectangle getLineBoundsInPixels(int lineIndex) {
996
1150
private Rectangle getLineBoundsInPixels (int lineIndex , long iter ) {
997
1151
if (iter == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
998
1152
PangoRectangle rect = new PangoRectangle ();
999
- OS .pango_layout_iter_get_line_extents (iter , null , rect );
1153
+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , rect );
1000
1154
int x = OS .PANGO_PIXELS (rect .x );
1001
1155
int y = OS .PANGO_PIXELS (rect .y );
1002
1156
int width = OS .PANGO_PIXELS (rect .width );
@@ -1073,6 +1227,10 @@ public int getLineIndex(int offset) {
1073
1227
* </ul>
1074
1228
*/
1075
1229
public FontMetrics getLineMetrics (int lineIndex ) {
1230
+ if (metricsAdapter .isFixedMetrics ()) {
1231
+ return metricsAdapter .getFixedLineMetrics (getDevice ());
1232
+ }
1233
+
1076
1234
checkLayout ();
1077
1235
computeRuns ();
1078
1236
int lineCount = OS .pango_layout_get_line_count (layout );
@@ -1092,7 +1250,7 @@ public FontMetrics getLineMetrics (int lineIndex) {
1092
1250
OS .pango_font_metrics_unref (metrics );
1093
1251
} else {
1094
1252
PangoRectangle rect = new PangoRectangle ();
1095
- OS .pango_layout_line_get_extents (OS .pango_layout_get_line (layout , lineIndex ), null , rect );
1253
+ metricsAdapter .pango_layout_line_get_extents (OS .pango_layout_get_line (layout , lineIndex ), null , rect );
1096
1254
ascentInPoints = DPIUtil .autoScaleDown (getDevice (), OS .PANGO_PIXELS (-rect .y ));
1097
1255
heightInPoints = DPIUtil .autoScaleDown (getDevice (), OS .PANGO_PIXELS (rect .height ));
1098
1256
}
@@ -1346,7 +1504,7 @@ int getOffsetInPixels(int x, int y, int[] trailing) {
1346
1504
if (iter == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
1347
1505
PangoRectangle rect = new PangoRectangle ();
1348
1506
do {
1349
- OS .pango_layout_iter_get_line_extents (iter , null , rect );
1507
+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , rect );
1350
1508
rect .y = OS .PANGO_PIXELS (rect .y );
1351
1509
rect .height = OS .PANGO_PIXELS (rect .height );
1352
1510
if (rect .y <= y && y < rect .y + rect .height ) {
@@ -1828,8 +1986,7 @@ public void setDescent (int descent) {
1828
1986
* @since 3.125
1829
1987
*/
1830
1988
public void setFixedLineMetrics (FontMetrics metrics ) {
1831
- if (metrics == null ) return ;
1832
- SWT .error (SWT .ERROR_NOT_IMPLEMENTED );
1989
+ metricsAdapter .setFixedLineMetrics (getDevice (), metrics );
1833
1990
}
1834
1991
1835
1992
/**
0 commit comments