hb-shape.cc (12862B)
1 /* 2 * Copyright © 2009 Red Hat, Inc. 3 * Copyright © 2012 Google, Inc. 4 * 5 * This is part of HarfBuzz, a text shaping library. 6 * 7 * Permission is hereby granted, without written agreement and without 8 * license or royalty fees, to use, copy, modify, and distribute this 9 * software and its documentation for any purpose, provided that the 10 * above copyright notice and the following two paragraphs appear in 11 * all copies of this software. 12 * 13 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR 14 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 15 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN 16 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 17 * DAMAGE. 18 * 19 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, 20 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 22 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO 23 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 24 * 25 * Red Hat Author(s): Behdad Esfahbod 26 * Google Author(s): Behdad Esfahbod 27 */ 28 29 #include "hb.hh" 30 31 #include "hb-shaper.hh" 32 #include "hb-shape-plan.hh" 33 #include "hb-buffer.hh" 34 #include "hb-font.hh" 35 #include "hb-machinery.hh" 36 37 38 #ifndef HB_NO_SHAPER 39 40 /** 41 * SECTION:hb-shape 42 * @title: hb-shape 43 * @short_description: Conversion of text strings into positioned glyphs 44 * @include: hb.h 45 * 46 * Shaping is the central operation of HarfBuzz. Shaping operates on buffers, 47 * which are sequences of Unicode characters that use the same font and have 48 * the same text direction, script, and language. After shaping the buffer 49 * contains the output glyphs and their positions. 50 **/ 51 52 53 static inline void free_static_shaper_list (); 54 55 static const char * const nil_shaper_list[] = {nullptr}; 56 57 static struct hb_shaper_list_lazy_loader_t : hb_lazy_loader_t<const char *, 58 hb_shaper_list_lazy_loader_t> 59 { 60 static const char ** create () 61 { 62 const char **shaper_list = (const char **) hb_calloc (1 + HB_SHAPERS_COUNT, sizeof (const char *)); 63 if (unlikely (!shaper_list)) 64 return nullptr; 65 66 const hb_shaper_entry_t *shapers = _hb_shapers_get (); 67 unsigned int i; 68 for (i = 0; i < HB_SHAPERS_COUNT; i++) 69 shaper_list[i] = shapers[i].name; 70 shaper_list[i] = nullptr; 71 72 hb_atexit (free_static_shaper_list); 73 74 return shaper_list; 75 } 76 static void destroy (const char **l) 77 { hb_free (l); } 78 static const char * const * get_null () 79 { return nil_shaper_list; } 80 } static_shaper_list; 81 82 static inline 83 void free_static_shaper_list () 84 { 85 static_shaper_list.free_instance (); 86 } 87 88 89 /** 90 * hb_shape_list_shapers: 91 * 92 * Retrieves the list of shapers supported by HarfBuzz. 93 * 94 * Return value: (transfer none) (array zero-terminated=1): a 95 * `NULL`-terminated array of supported shapers constant string. 96 * The returned array is owned by HarfBuzz and should not be 97 * modified or freed. 98 * 99 * Since: 0.9.2 100 **/ 101 const char ** 102 hb_shape_list_shapers () 103 { 104 return static_shaper_list.get_unconst (); 105 } 106 107 108 /** 109 * hb_shape_full: 110 * @font: an #hb_font_t to use for shaping 111 * @buffer: an #hb_buffer_t to shape 112 * @features: (array length=num_features) (nullable): an array of user 113 * specified #hb_feature_t or `NULL` 114 * @num_features: the length of @features array 115 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated 116 * array of shapers to use or `NULL` 117 * 118 * See hb_shape() for details. If @shaper_list is not `NULL`, the specified 119 * shapers will be used in the given order, otherwise the default shapers list 120 * will be used. 121 * 122 * Return value: false if all shapers failed, true otherwise 123 * 124 * Since: 0.9.2 125 **/ 126 hb_bool_t 127 hb_shape_full (hb_font_t *font, 128 hb_buffer_t *buffer, 129 const hb_feature_t *features, 130 unsigned int num_features, 131 const char * const *shaper_list) 132 { 133 if (unlikely (!buffer->len)) 134 return true; 135 136 buffer->enter (); 137 138 hb_buffer_t *text_buffer = nullptr; 139 if (buffer->flags & HB_BUFFER_FLAG_VERIFY) 140 { 141 text_buffer = hb_buffer_create (); 142 hb_buffer_append (text_buffer, buffer, 0, -1); 143 } 144 145 hb_shape_plan_t *shape_plan = hb_shape_plan_create_cached2 (font->face, &buffer->props, 146 features, num_features, 147 font->coords, font->num_coords, 148 shaper_list); 149 150 hb_bool_t res = hb_shape_plan_execute (shape_plan, font, buffer, features, num_features); 151 152 hb_shape_plan_destroy (shape_plan); 153 154 if (text_buffer) 155 { 156 if (res && buffer->successful 157 && text_buffer->successful 158 && !buffer->verify (text_buffer, 159 font, 160 features, 161 num_features, 162 shaper_list)) 163 res = false; 164 hb_buffer_destroy (text_buffer); 165 } 166 167 buffer->leave (); 168 169 return res; 170 } 171 172 /** 173 * hb_shape: 174 * @font: an #hb_font_t to use for shaping 175 * @buffer: an #hb_buffer_t to shape 176 * @features: (array length=num_features) (nullable): an array of user 177 * specified #hb_feature_t or `NULL` 178 * @num_features: the length of @features array 179 * 180 * Shapes @buffer using @font turning its Unicode characters content to 181 * positioned glyphs. If @features is not `NULL`, it will be used to control the 182 * features applied during shaping. If two @features have the same tag but 183 * overlapping ranges the value of the feature with the higher index takes 184 * precedence. 185 * 186 * Since: 0.9.2 187 **/ 188 void 189 hb_shape (hb_font_t *font, 190 hb_buffer_t *buffer, 191 const hb_feature_t *features, 192 unsigned int num_features) 193 { 194 hb_shape_full (font, buffer, features, num_features, nullptr); 195 } 196 197 198 #ifdef HB_EXPERIMENTAL_API 199 #ifndef HB_NO_VAR 200 201 static float 202 buffer_advance (hb_buffer_t *buffer) 203 { 204 float a = 0; 205 auto *pos = buffer->pos; 206 unsigned count = buffer->len; 207 if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction)) 208 for (unsigned i = 0; i < count; i++) 209 a += pos[i].x_advance; 210 else 211 for (unsigned i = 0; i < count; i++) 212 a += pos[i].y_advance; 213 return a; 214 } 215 216 static void 217 reset_buffer (hb_buffer_t *buffer, 218 hb_array_t<const hb_glyph_info_t> text) 219 { 220 assert (buffer->ensure (text.length)); 221 buffer->have_positions = false; 222 buffer->len = text.length; 223 hb_memcpy (buffer->info, text.arrayZ, text.length * sizeof (buffer->info[0])); 224 hb_buffer_set_content_type (buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); 225 } 226 227 /** 228 * hb_shape_justify: 229 * @font: a mutable #hb_font_t to use for shaping 230 * @buffer: an #hb_buffer_t to shape 231 * @features: (array length=num_features) (nullable): an array of user 232 * specified #hb_feature_t or `NULL` 233 * @num_features: the length of @features array 234 * @shaper_list: (array zero-terminated=1) (nullable): a `NULL`-terminated 235 * array of shapers to use or `NULL` 236 * @min_target_advance: Minimum advance width/height to aim for. 237 * @max_target_advance: Maximum advance width/height to aim for. 238 * @advance: (inout): Input/output advance width/height of the buffer. 239 * @var_tag: (out): Variation-axis tag used for justification. 240 * @var_value: (out): Variation-axis value used to reach target justification. 241 * 242 * See hb_shape_full() for basic details. If @shaper_list is not `NULL`, the specified 243 * shapers will be used in the given order, otherwise the default shapers list 244 * will be used. 245 * 246 * In addition, justify the shaping results such that the shaping results reach 247 * the target advance width/height, depending on the buffer direction. 248 * 249 * If the advance of the buffer shaped with hb_shape_full() is already known, 250 * put that in *advance. Otherwise set *advance to zero. 251 * 252 * This API is currently experimental and will probably change in the future. 253 * 254 * Return value: false if all shapers failed, true otherwise 255 * 256 * XSince: EXPERIMENTAL 257 **/ 258 hb_bool_t 259 hb_shape_justify (hb_font_t *font, 260 hb_buffer_t *buffer, 261 const hb_feature_t *features, 262 unsigned int num_features, 263 const char * const *shaper_list, 264 float min_target_advance, 265 float max_target_advance, 266 float *advance, /* IN/OUT */ 267 hb_tag_t *var_tag, /* OUT */ 268 float *var_value /* OUT */) 269 { 270 // TODO Negative font scales? 271 272 /* If default advance already matches target, nothing to do. Shape and return. */ 273 if (min_target_advance <= *advance && *advance <= max_target_advance) 274 { 275 *var_tag = HB_TAG_NONE; 276 *var_value = 0.0f; 277 return hb_shape_full (font, buffer, 278 features, num_features, 279 shaper_list); 280 } 281 282 hb_face_t *face = font->face; 283 284 /* Choose variation tag to use for justification. */ 285 286 hb_tag_t tag = HB_TAG_NONE; 287 hb_ot_var_axis_info_t axis_info; 288 289 hb_tag_t tags[] = 290 { 291 HB_TAG ('j','s','t','f'), 292 HB_TAG ('w','d','t','h'), 293 }; 294 for (unsigned i = 0; i < ARRAY_LENGTH (tags); i++) 295 if (hb_ot_var_find_axis_info (face, tags[i], &axis_info)) 296 { 297 tag = *var_tag = tags[i]; 298 break; 299 } 300 301 /* If no suitable variation axis found, can't justify. Just shape and return. */ 302 if (!tag) 303 { 304 *var_tag = HB_TAG_NONE; 305 *var_value = 0.0f; 306 if (hb_shape_full (font, buffer, 307 features, num_features, 308 shaper_list)) 309 { 310 *advance = buffer_advance (buffer); 311 return true; 312 } 313 else 314 return false; 315 } 316 317 /* Copy buffer text as we need it so we can shape multiple times. */ 318 unsigned text_len = buffer->len; 319 auto *text_info = (hb_glyph_info_t *) hb_malloc (text_len * sizeof (buffer->info[0])); 320 if (unlikely (text_len && !text_info)) 321 return false; 322 hb_memcpy (text_info, buffer->info, text_len * sizeof (buffer->info[0])); 323 auto text = hb_array<const hb_glyph_info_t> (text_info, text_len); 324 325 /* If default advance was not provided to us, calculate it. */ 326 if (!*advance) 327 { 328 hb_font_set_variation (font, tag, axis_info.default_value); 329 if (!hb_shape_full (font, buffer, 330 features, num_features, 331 shaper_list)) 332 return false; 333 *advance = buffer_advance (buffer); 334 } 335 336 /* If default advance already matches target, nothing to do. Shape and return. 337 * Do this again, in case advance was just calculated. 338 */ 339 if (min_target_advance <= *advance && *advance <= max_target_advance) 340 { 341 *var_tag = HB_TAG_NONE; 342 *var_value = 0.0f; 343 return true; 344 } 345 346 /* Prepare for running the solver. */ 347 double a, b, ya, yb; 348 if (*advance < min_target_advance) 349 { 350 /* Need to expand. */ 351 ya = (double) *advance; 352 a = (double) axis_info.default_value; 353 b = (double) axis_info.max_value; 354 355 /* Shape buffer for maximum expansion to use as other 356 * starting point for the solver. */ 357 hb_font_set_variation (font, tag, (float) b); 358 reset_buffer (buffer, text); 359 if (!hb_shape_full (font, buffer, 360 features, num_features, 361 shaper_list)) 362 return false; 363 yb = (double) buffer_advance (buffer); 364 /* If the maximum expansion is less than max target, 365 * there's nothing to solve for. Just return it. */ 366 if (yb <= (double) max_target_advance) 367 { 368 *var_value = (float) b; 369 *advance = (float) yb; 370 return true; 371 } 372 } 373 else 374 { 375 /* Need to shrink. */ 376 yb = (double) *advance; 377 a = (double) axis_info.min_value; 378 b = (double) axis_info.default_value; 379 380 /* Shape buffer for maximum shrinkate to use as other 381 * starting point for the solver. */ 382 hb_font_set_variation (font, tag, (float) a); 383 reset_buffer (buffer, text); 384 if (!hb_shape_full (font, buffer, 385 features, num_features, 386 shaper_list)) 387 return false; 388 ya = (double) buffer_advance (buffer); 389 /* If the maximum shrinkate is more than min target, 390 * there's nothing to solve for. Just return it. */ 391 if (ya >= (double) min_target_advance) 392 { 393 *var_value = (float) a; 394 *advance = (float) ya; 395 return true; 396 } 397 } 398 399 /* Run the solver to find a var axis value that hits 400 * the desired width. */ 401 402 double epsilon = (b - a) / (1<<14); 403 bool failed = false; 404 405 auto f = [&] (double x) 406 { 407 hb_font_set_variation (font, tag, (float) x); 408 reset_buffer (buffer, text); 409 if (unlikely (!hb_shape_full (font, buffer, 410 features, num_features, 411 shaper_list))) 412 { 413 failed = true; 414 return (double) min_target_advance; 415 } 416 417 double w = (double) buffer_advance (buffer); 418 DEBUG_MSG (JUSTIFY, nullptr, "Trying '%c%c%c%c' axis parameter %f. Advance %g. Target: min %g max %g", 419 HB_UNTAG (tag), x, w, 420 (double) min_target_advance, (double) max_target_advance); 421 return w; 422 }; 423 424 double y = 0; 425 double itp = solve_itp (f, 426 a, b, 427 epsilon, 428 (double) min_target_advance, (double) max_target_advance, 429 ya, yb, y); 430 431 hb_free (text_info); 432 433 if (failed) 434 return false; 435 436 *var_value = (float) itp; 437 *advance = (float) y; 438 439 return true; 440 } 441 #endif 442 #endif 443 444 445 #endif