@@ -63,8 +63,160 @@ public String toString () {
6363 long layout , context , attrList , selAttrList ;
6464 int [] invalidOffsets ;
6565 int verticalIndentInPoints ;
66+ MetricsAdapter metricsAdapter = new MetricsAdapter ();
6667 static final char LTR_MARK = '\u200E' , RTL_MARK = '\u200F' , ZWS = '\u200B' , ZWNBS = '\uFEFF' ;
6768
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+
68220/**
69221 * Constructs a new instance of this class on the given device.
70222 * <p>
@@ -137,7 +289,12 @@ void computeRuns () {
137289 int nSegments = segementsLength - text .length ();
138290 int offsetCount = nSegments ;
139291 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 ) {
141298 PangoRectangle rect = new PangoRectangle ();
142299 if (ascentInPoints != -1 ) rect .y = -(DPIUtil .autoScaleUp (getDevice (), ascentInPoints ) * OS .PANGO_SCALE );
143300 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
502659 int lineIndex = 0 ;
503660 do {
504661 int lineEnd ;
505- OS .pango_layout_iter_get_line_extents (iter , null , rect );
662+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , rect );
506663 if (OS .pango_layout_iter_next_line (iter )) {
507664 int bytePos = OS .pango_layout_iter_get_index (iter );
508665 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
547704 Cairo .cairo_scale (cairo , -1 , 1 );
548705 Cairo .cairo_translate (cairo , -2 * x - width (), 0 );
549706 }
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 );
552708 drawBorder (gc , x , y , null );
553709 if ((data .style & SWT .MIRRORED ) != 0 ) {
554710 Cairo .cairo_restore (cairo );
@@ -601,12 +757,11 @@ void drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelectio
601757 long cairo = data .cairo ;
602758 Cairo .cairo_save (cairo );
603759 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 );
606761 drawBorder (gc , x , y , null );
607762 }
608763 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 );
610765 if (rgn != 0 ) {
611766 GDK .gdk_cairo_region (cairo , rgn );
612767 Cairo .cairo_clip (cairo );
@@ -615,9 +770,8 @@ void drawWithCairo(GC gc, int x, int y, int start, int end, boolean fullSelectio
615770 Cairo .cairo_region_destroy (rgn );
616771 }
617772 Cairo .cairo_set_source_rgba (cairo , fg .red , fg .green , fg .blue , fg .alpha );
618- Cairo .cairo_move_to (cairo , x , y );
619773 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 );
621775 OS .pango_layout_set_attributes (layout , attrList );
622776 drawBorder (gc , x , y , fg );
623777 Cairo .cairo_restore (cairo );
@@ -643,7 +797,7 @@ void drawBorder(GC gc, int x, int y, GdkRGBA selectionColor) {
643797 int byteStart = (int )(OS .g_utf16_offset_to_pointer (ptr , start ) - ptr );
644798 int byteEnd = (int )(OS .g_utf16_offset_to_pointer (ptr , end + 1 ) - ptr );
645799 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 );
647801 if (rgn != 0 ) {
648802 int [] nRects = new int [1 ];
649803 long [] rects = new long [1 ];
@@ -761,7 +915,7 @@ Rectangle getBoundsInPixels(int spacingInPixels) {
761915 checkLayout ();
762916 computeRuns ();
763917 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 );
765919 int wrapWidth = OS .pango_layout_get_width (layout );
766920 w [0 ] = wrapWidth != -1 ? wrapWidth : w [0 ] + OS .pango_layout_get_indent (layout );
767921 int width = OS .PANGO_PIXELS (w [0 ]);
@@ -809,7 +963,7 @@ Rectangle getBoundsInPixels(int start, int end) {
809963 byteStart = Math .min (byteStart , strlen );
810964 byteEnd = Math .min (byteEnd , strlen );
811965 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 );
813967 if (clipRegion == 0 ) return new Rectangle (0 , 0 , 0 , 0 );
814968 cairo_rectangle_int_t rect = new cairo_rectangle_int_t ();
815969
@@ -825,7 +979,7 @@ Rectangle getBoundsInPixels(int start, int end) {
825979 if (linesRegion == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
826980 int lineEnd = 0 ;
827981 do {
828- OS .pango_layout_iter_get_line_extents (iter , null , pangoRect );
982+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , pangoRect );
829983 if (OS .pango_layout_iter_next_line (iter )) {
830984 lineEnd = OS .pango_layout_iter_get_index (iter ) - 1 ;
831985 } else {
@@ -996,7 +1150,7 @@ Rectangle getLineBoundsInPixels(int lineIndex) {
9961150private Rectangle getLineBoundsInPixels (int lineIndex , long iter ) {
9971151 if (iter == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
9981152 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 );
10001154 int x = OS .PANGO_PIXELS (rect .x );
10011155 int y = OS .PANGO_PIXELS (rect .y );
10021156 int width = OS .PANGO_PIXELS (rect .width );
@@ -1073,6 +1227,10 @@ public int getLineIndex(int offset) {
10731227 * </ul>
10741228 */
10751229public FontMetrics getLineMetrics (int lineIndex ) {
1230+ if (metricsAdapter .isFixedMetrics ()) {
1231+ return metricsAdapter .getFixedLineMetrics (getDevice ());
1232+ }
1233+
10761234 checkLayout ();
10771235 computeRuns ();
10781236 int lineCount = OS .pango_layout_get_line_count (layout );
@@ -1092,7 +1250,7 @@ public FontMetrics getLineMetrics (int lineIndex) {
10921250 OS .pango_font_metrics_unref (metrics );
10931251 } else {
10941252 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 );
10961254 ascentInPoints = DPIUtil .autoScaleDown (getDevice (), OS .PANGO_PIXELS (-rect .y ));
10971255 heightInPoints = DPIUtil .autoScaleDown (getDevice (), OS .PANGO_PIXELS (rect .height ));
10981256 }
@@ -1346,7 +1504,7 @@ int getOffsetInPixels(int x, int y, int[] trailing) {
13461504 if (iter == 0 ) SWT .error (SWT .ERROR_NO_HANDLES );
13471505 PangoRectangle rect = new PangoRectangle ();
13481506 do {
1349- OS .pango_layout_iter_get_line_extents (iter , null , rect );
1507+ metricsAdapter .pango_layout_iter_get_line_extents (iter , null , rect );
13501508 rect .y = OS .PANGO_PIXELS (rect .y );
13511509 rect .height = OS .PANGO_PIXELS (rect .height );
13521510 if (rect .y <= y && y < rect .y + rect .height ) {
@@ -1828,8 +1986,7 @@ public void setDescent (int descent) {
18281986 * @since 3.125
18291987 */
18301988public void setFixedLineMetrics (FontMetrics metrics ) {
1831- if (metrics == null ) return ;
1832- SWT .error (SWT .ERROR_NOT_IMPLEMENTED );
1989+ metricsAdapter .setFixedLineMetrics (getDevice (), metrics );
18331990}
18341991
18351992/**
0 commit comments