1 | /* Pango |
2 | * pango-item.c: Single run handling |
3 | * |
4 | * Copyright (C) 2000 Red Hat Software |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this library; if not, write to the |
18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
19 | * Boston, MA 02111-1307, USA. |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #include "pango-attributes.h" |
24 | #include "pango-item-private.h" |
25 | #include "pango-impl-utils.h" |
26 | |
27 | /** |
28 | * pango_item_new: |
29 | * |
30 | * Creates a new `PangoItem` structure initialized to default values. |
31 | * |
32 | * Return value: the newly allocated `PangoItem`, which should |
33 | * be freed with [method@Pango.Item.free]. |
34 | */ |
35 | PangoItem * |
36 | pango_item_new (void) |
37 | { |
38 | PangoItemPrivate *result = g_slice_new0 (PangoItemPrivate); |
39 | |
40 | result->analysis.flags |= PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET; |
41 | |
42 | return (PangoItem *)result; |
43 | } |
44 | |
45 | /** |
46 | * pango_item_copy: |
47 | * @item: (nullable): a `PangoItem` |
48 | * |
49 | * Copy an existing `PangoItem` structure. |
50 | * |
51 | * Return value: (nullable): the newly allocated `PangoItem` |
52 | */ |
53 | PangoItem * |
54 | pango_item_copy (PangoItem *item) |
55 | { |
56 | GSList *, *tmp_list; |
57 | PangoItem *result; |
58 | |
59 | if (item == NULL) |
60 | return NULL; |
61 | |
62 | result = pango_item_new (); |
63 | |
64 | result->offset = item->offset; |
65 | result->length = item->length; |
66 | result->num_chars = item->num_chars; |
67 | if (item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET) |
68 | ((PangoItemPrivate *)result)->char_offset = ((PangoItemPrivate *)item)->char_offset; |
69 | |
70 | result->analysis = item->analysis; |
71 | if (result->analysis.lang_engine) |
72 | g_object_ref (result->analysis.lang_engine); |
73 | |
74 | if (result->analysis.font) |
75 | g_object_ref (result->analysis.font); |
76 | |
77 | extra_attrs = NULL; |
78 | tmp_list = item->analysis.extra_attrs; |
79 | while (tmp_list) |
80 | { |
81 | extra_attrs = g_slist_prepend (list: extra_attrs, data: pango_attribute_copy (attr: tmp_list->data)); |
82 | tmp_list = tmp_list->next; |
83 | } |
84 | |
85 | result->analysis.extra_attrs = g_slist_reverse (list: extra_attrs); |
86 | |
87 | return result; |
88 | } |
89 | |
90 | /** |
91 | * pango_item_free: |
92 | * @item: (nullable): a `PangoItem`, may be %NULL |
93 | * |
94 | * Free a `PangoItem` and all associated memory. |
95 | **/ |
96 | void |
97 | pango_item_free (PangoItem *item) |
98 | { |
99 | if (item == NULL) |
100 | return; |
101 | |
102 | if (item->analysis.extra_attrs) |
103 | { |
104 | g_slist_foreach (list: item->analysis.extra_attrs, func: (GFunc)pango_attribute_destroy, NULL); |
105 | g_slist_free (list: item->analysis.extra_attrs); |
106 | } |
107 | |
108 | if (item->analysis.lang_engine) |
109 | g_object_unref (object: item->analysis.lang_engine); |
110 | |
111 | if (item->analysis.font) |
112 | g_object_unref (object: item->analysis.font); |
113 | |
114 | if (item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET) |
115 | g_slice_free (PangoItemPrivate, (PangoItemPrivate *)item); |
116 | else |
117 | g_slice_free (PangoItem, item); |
118 | } |
119 | |
120 | G_DEFINE_BOXED_TYPE (PangoItem, pango_item, |
121 | pango_item_copy, |
122 | pango_item_free); |
123 | |
124 | /** |
125 | * pango_item_split: |
126 | * @orig: a `PangoItem` |
127 | * @split_index: byte index of position to split item, relative to the |
128 | * start of the item |
129 | * @split_offset: number of chars between start of @orig and @split_index |
130 | * |
131 | * Modifies @orig to cover only the text after @split_index, and |
132 | * returns a new item that covers the text before @split_index that |
133 | * used to be in @orig. |
134 | * |
135 | * You can think of @split_index as the length of the returned item. |
136 | * @split_index may not be 0, and it may not be greater than or equal |
137 | * to the length of @orig (that is, there must be at least one byte |
138 | * assigned to each item, you can't create a zero-length item). |
139 | * @split_offset is the length of the first item in chars, and must be |
140 | * provided because the text used to generate the item isn't available, |
141 | * so `pango_item_split()` can't count the char length of the split items |
142 | * itself. |
143 | * |
144 | * Return value: new item representing text before @split_index, which |
145 | * should be freed with [method@Pango.Item.free]. |
146 | */ |
147 | PangoItem * |
148 | pango_item_split (PangoItem *orig, |
149 | int split_index, |
150 | int split_offset) |
151 | { |
152 | PangoItem *new_item; |
153 | |
154 | g_return_val_if_fail (orig != NULL, NULL); |
155 | g_return_val_if_fail (split_index > 0, NULL); |
156 | g_return_val_if_fail (split_index < orig->length, NULL); |
157 | g_return_val_if_fail (split_offset > 0, NULL); |
158 | g_return_val_if_fail (split_offset < orig->num_chars, NULL); |
159 | |
160 | new_item = pango_item_copy (item: orig); |
161 | new_item->length = split_index; |
162 | new_item->num_chars = split_offset; |
163 | |
164 | orig->offset += split_index; |
165 | orig->length -= split_index; |
166 | orig->num_chars -= split_offset; |
167 | if (orig->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET) |
168 | ((PangoItemPrivate *)orig)->char_offset += split_offset; |
169 | |
170 | return new_item; |
171 | } |
172 | |
173 | /*< private > |
174 | * pango_item_unsplit: |
175 | * @orig: the item to unsplit |
176 | * @split_index: value passed to pango_item_split() |
177 | * @split_offset: value passed to pango_item_split() |
178 | * |
179 | * Undoes the effect of a pango_item_split() call with |
180 | * the same arguments. |
181 | * |
182 | * You are expected to free the new item that was returned |
183 | * by pango_item_split() yourself. |
184 | */ |
185 | void |
186 | pango_item_unsplit (PangoItem *orig, |
187 | int split_index, |
188 | int split_offset) |
189 | { |
190 | orig->offset -= split_index; |
191 | orig->length += split_index; |
192 | orig->num_chars += split_offset; |
193 | |
194 | if (orig->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET) |
195 | ((PangoItemPrivate *)orig)->char_offset -= split_offset; |
196 | } |
197 | |
198 | static int |
199 | compare_attr (gconstpointer p1, gconstpointer p2) |
200 | { |
201 | const PangoAttribute *a1 = p1; |
202 | const PangoAttribute *a2 = p2; |
203 | if (pango_attribute_equal (attr1: a1, attr2: a2) && |
204 | a1->start_index == a2->start_index && |
205 | a1->end_index == a2->end_index) |
206 | return 0; |
207 | |
208 | return 1; |
209 | } |
210 | |
211 | /** |
212 | * pango_item_apply_attrs: |
213 | * @item: a `PangoItem` |
214 | * @iter: a `PangoAttrIterator` |
215 | * |
216 | * Add attributes to a `PangoItem`. |
217 | * |
218 | * The idea is that you have attributes that don't affect itemization, |
219 | * such as font features, so you filter them out using |
220 | * [method@Pango.AttrList.filter], itemize your text, then reapply the |
221 | * attributes to the resulting items using this function. |
222 | * |
223 | * The @iter should be positioned before the range of the item, |
224 | * and will be advanced past it. This function is meant to be called |
225 | * in a loop over the items resulting from itemization, while passing |
226 | * the iter to each call. |
227 | * |
228 | * Since: 1.44 |
229 | */ |
230 | void |
231 | pango_item_apply_attrs (PangoItem *item, |
232 | PangoAttrIterator *iter) |
233 | { |
234 | int start, end; |
235 | GSList *attrs = NULL; |
236 | |
237 | do |
238 | { |
239 | pango_attr_iterator_range (iterator: iter, start: &start, end: &end); |
240 | |
241 | if (start >= item->offset + item->length) |
242 | break; |
243 | |
244 | if (end >= item->offset) |
245 | { |
246 | GSList *list, *l; |
247 | |
248 | list = pango_attr_iterator_get_attrs (iterator: iter); |
249 | for (l = list; l; l = l->next) |
250 | { |
251 | if (!g_slist_find_custom (list: attrs, data: l->data, func: compare_attr)) |
252 | |
253 | attrs = g_slist_prepend (list: attrs, data: pango_attribute_copy (attr: l->data)); |
254 | } |
255 | g_slist_free_full (list, free_func: (GDestroyNotify)pango_attribute_destroy); |
256 | } |
257 | |
258 | if (end >= item->offset + item->length) |
259 | break; |
260 | } |
261 | while (pango_attr_iterator_next (iterator: iter)); |
262 | |
263 | item->analysis.extra_attrs = g_slist_concat (list1: item->analysis.extra_attrs, list2: attrs); |
264 | } |
265 | |
266 | void |
267 | pango_analysis_collect_features (const PangoAnalysis *analysis, |
268 | hb_feature_t *features, |
269 | guint length, |
270 | guint *num_features) |
271 | { |
272 | GSList *l; |
273 | |
274 | pango_font_get_features (font: analysis->font, features, len: length, num_features); |
275 | |
276 | for (l = analysis->extra_attrs; l && *num_features < length; l = l->next) |
277 | { |
278 | PangoAttribute *attr = l->data; |
279 | if (attr->klass->type == PANGO_ATTR_FONT_FEATURES) |
280 | { |
281 | PangoAttrFontFeatures *fattr = (PangoAttrFontFeatures *) attr; |
282 | const gchar *feat; |
283 | const gchar *end; |
284 | int len; |
285 | |
286 | feat = fattr->features; |
287 | |
288 | while (feat != NULL && *num_features < length) |
289 | { |
290 | end = strchr (s: feat, c: ','); |
291 | if (end) |
292 | len = end - feat; |
293 | else |
294 | len = -1; |
295 | if (hb_feature_from_string (str: feat, len, feature: &features[*num_features])) |
296 | { |
297 | features[*num_features].start = attr->start_index; |
298 | features[*num_features].end = attr->end_index; |
299 | (*num_features)++; |
300 | } |
301 | |
302 | if (end == NULL) |
303 | break; |
304 | |
305 | feat = end + 1; |
306 | } |
307 | } |
308 | } |
309 | |
310 | /* Turn off ligatures when letterspacing */ |
311 | for (l = analysis->extra_attrs; l && *num_features < length; l = l->next) |
312 | { |
313 | PangoAttribute *attr = l->data; |
314 | if (attr->klass->type == PANGO_ATTR_LETTER_SPACING) |
315 | { |
316 | hb_tag_t tags[] = { |
317 | HB_TAG('l','i','g','a'), |
318 | HB_TAG('c','l','i','g'), |
319 | HB_TAG('d','l','i','g'), |
320 | HB_TAG('h','l','i','g'), |
321 | }; |
322 | int i; |
323 | for (i = 0; i < G_N_ELEMENTS (tags); i++) |
324 | { |
325 | features[*num_features].tag = tags[i]; |
326 | features[*num_features].value = 0; |
327 | features[*num_features].start = attr->start_index; |
328 | features[*num_features].end = attr->end_index; |
329 | (*num_features)++; |
330 | } |
331 | } |
332 | } |
333 | } |
334 | |
335 | /*< private > |
336 | * pango_analysis_set_size_font: |
337 | * @analysis: a `PangoAnalysis` |
338 | * @font: a `PangoFont` |
339 | * |
340 | * Sets the font to use for determining the line height. |
341 | * |
342 | * This is used when scaling fonts for emulated Small Caps, |
343 | * to preserve the original line height. |
344 | */ |
345 | void |
346 | pango_analysis_set_size_font (PangoAnalysis *analysis, |
347 | PangoFont *font) |
348 | { |
349 | PangoAnalysisPrivate *priv = (PangoAnalysisPrivate *)analysis; |
350 | |
351 | if (priv->size_font) |
352 | g_object_unref (object: priv->size_font); |
353 | priv->size_font = font; |
354 | if (priv->size_font) |
355 | g_object_ref (priv->size_font); |
356 | } |
357 | |
358 | /*< private > |
359 | * pango_analysis_get_size_font: |
360 | * @analysis: a `PangoAnalysis` |
361 | * |
362 | * Gets the font to use for determining line height. |
363 | * |
364 | * If this returns `NULL`, use analysis->font. |
365 | * |
366 | * Returns: (nullable) (transfer none): the font |
367 | */ |
368 | PangoFont * |
369 | pango_analysis_get_size_font (const PangoAnalysis *analysis) |
370 | { |
371 | PangoAnalysisPrivate *priv = (PangoAnalysisPrivate *)analysis; |
372 | |
373 | return priv->size_font; |
374 | } |
375 | |