Skip to content

Commit d6a8815

Browse files
committed
Issue #932: [GTK] implementation for StyledText.setFixedLineMetrics
Signed-off-by: Alexandr Miloslavskiy <[email protected]>
1 parent 44ccea9 commit d6a8815

File tree

1 file changed

+175
-18
lines changed
  • bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics

1 file changed

+175
-18
lines changed

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/TextLayout.java

Lines changed: 175 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {
9961150
private 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
*/
10751229
public 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
*/
18301988
public 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

Comments
 (0)