1/* Pango
2 * pango-matrix.c: Matrix manipulation routines
3 *
4 * Copyright (C) 2000, 2006 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 <stdlib.h>
24#include <math.h>
25
26#include "pango-matrix.h"
27#include "pango-impl-utils.h"
28
29G_DEFINE_BOXED_TYPE (PangoMatrix, pango_matrix,
30 pango_matrix_copy,
31 pango_matrix_free);
32
33/**
34 * pango_matrix_copy:
35 * @matrix: (nullable): a `PangoMatrix`
36 *
37 * Copies a `PangoMatrix`.
38 *
39 * Return value: (nullable): the newly allocated `PangoMatrix`
40 *
41 * Since: 1.6
42 */
43PangoMatrix *
44pango_matrix_copy (const PangoMatrix *matrix)
45{
46 PangoMatrix *new_matrix;
47
48 if (matrix == NULL)
49 return NULL;
50
51 new_matrix = g_slice_new (PangoMatrix);
52
53 *new_matrix = *matrix;
54
55 return new_matrix;
56}
57
58/**
59 * pango_matrix_free:
60 * @matrix: (nullable): a `PangoMatrix`, may be %NULL
61 *
62 * Free a `PangoMatrix`.
63 *
64 * Since: 1.6
65 */
66void
67pango_matrix_free (PangoMatrix *matrix)
68{
69 if (matrix == NULL)
70 return;
71
72 g_slice_free (PangoMatrix, matrix);
73}
74
75/**
76 * pango_matrix_translate:
77 * @matrix: a `PangoMatrix`
78 * @tx: amount to translate in the X direction
79 * @ty: amount to translate in the Y direction
80 *
81 * Changes the transformation represented by @matrix to be the
82 * transformation given by first translating by (@tx, @ty)
83 * then applying the original transformation.
84 *
85 * Since: 1.6
86 */
87void
88pango_matrix_translate (PangoMatrix *matrix,
89 double tx,
90 double ty)
91{
92 g_return_if_fail (matrix != NULL);
93
94 matrix->x0 = matrix->xx * tx + matrix->xy * ty + matrix->x0;
95 matrix->y0 = matrix->yx * tx + matrix->yy * ty + matrix->y0;
96}
97
98/**
99 * pango_matrix_scale:
100 * @matrix: a `PangoMatrix`
101 * @scale_x: amount to scale by in X direction
102 * @scale_y: amount to scale by in Y direction
103 *
104 * Changes the transformation represented by @matrix to be the
105 * transformation given by first scaling by @sx in the X direction
106 * and @sy in the Y direction then applying the original
107 * transformation.
108 *
109 * Since: 1.6
110 */
111void
112pango_matrix_scale (PangoMatrix *matrix,
113 double scale_x,
114 double scale_y)
115{
116 g_return_if_fail (matrix != NULL);
117
118 matrix->xx *= scale_x;
119 matrix->xy *= scale_y;
120 matrix->yx *= scale_x;
121 matrix->yy *= scale_y;
122}
123
124/**
125 * pango_matrix_rotate:
126 * @matrix: a `PangoMatrix`
127 * @degrees: degrees to rotate counter-clockwise
128 *
129 * Changes the transformation represented by @matrix to be the
130 * transformation given by first rotating by @degrees degrees
131 * counter-clockwise then applying the original transformation.
132 *
133 * Since: 1.6
134 */
135void
136pango_matrix_rotate (PangoMatrix *matrix,
137 double degrees)
138{
139 PangoMatrix tmp;
140 gdouble r, s, c;
141
142 g_return_if_fail (matrix != NULL);
143
144 r = degrees * (G_PI / 180.);
145 s = sin (x: r);
146 c = cos (x: r);
147
148 tmp.xx = c;
149 tmp.xy = s;
150 tmp.yx = -s;
151 tmp.yy = c;
152 tmp.x0 = 0;
153 tmp.y0 = 0;
154
155 pango_matrix_concat (matrix, new_matrix: &tmp);
156}
157
158/**
159 * pango_matrix_concat:
160 * @matrix: a `PangoMatrix`
161 * @new_matrix: a `PangoMatrix`
162 *
163 * Changes the transformation represented by @matrix to be the
164 * transformation given by first applying transformation
165 * given by @new_matrix then applying the original transformation.
166 *
167 * Since: 1.6
168 */
169void
170pango_matrix_concat (PangoMatrix *matrix,
171 const PangoMatrix *new_matrix)
172{
173 PangoMatrix tmp;
174
175 g_return_if_fail (matrix != NULL);
176
177 tmp = *matrix;
178
179 matrix->xx = tmp.xx * new_matrix->xx + tmp.xy * new_matrix->yx;
180 matrix->xy = tmp.xx * new_matrix->xy + tmp.xy * new_matrix->yy;
181 matrix->yx = tmp.yx * new_matrix->xx + tmp.yy * new_matrix->yx;
182 matrix->yy = tmp.yx * new_matrix->xy + tmp.yy * new_matrix->yy;
183 matrix->x0 = tmp.xx * new_matrix->x0 + tmp.xy * new_matrix->y0 + tmp.x0;
184 matrix->y0 = tmp.yx * new_matrix->x0 + tmp.yy * new_matrix->y0 + tmp.y0;
185}
186
187/**
188 * pango_matrix_get_font_scale_factor:
189 * @matrix: (nullable): a `PangoMatrix`, may be %NULL
190 *
191 * Returns the scale factor of a matrix on the height of the font.
192 *
193 * That is, the scale factor in the direction perpendicular to the
194 * vector that the X coordinate is mapped to. If the scale in the X
195 * coordinate is needed as well, use [method@Pango.Matrix.get_font_scale_factors].
196 *
197 * Return value: the scale factor of @matrix on the height of the font,
198 * or 1.0 if @matrix is %NULL.
199 *
200 * Since: 1.12
201 */
202double
203pango_matrix_get_font_scale_factor (const PangoMatrix *matrix)
204{
205 double yscale;
206 pango_matrix_get_font_scale_factors (matrix, NULL, yscale: &yscale);
207 return yscale;
208}
209
210/**
211 * pango_matrix_get_font_scale_factors:
212 * @matrix: (nullable): a `PangoMatrix`
213 * @xscale: (out) (optional): output scale factor in the x direction
214 * @yscale: (out) (optional): output scale factor perpendicular to the x direction
215 *
216 * Calculates the scale factor of a matrix on the width and height of the font.
217 *
218 * That is, @xscale is the scale factor in the direction of the X coordinate,
219 * and @yscale is the scale factor in the direction perpendicular to the
220 * vector that the X coordinate is mapped to.
221 *
222 * Note that output numbers will always be non-negative.
223 *
224 * Since: 1.38
225 **/
226void
227pango_matrix_get_font_scale_factors (const PangoMatrix *matrix,
228 double *xscale, double *yscale)
229{
230/*
231 * Based on cairo-matrix.c:_cairo_matrix_compute_scale_factors()
232 *
233 * Copyright 2005, Keith Packard
234 */
235 double major = 1., minor = 1.;
236
237 if (matrix)
238 {
239 double x = matrix->xx;
240 double y = matrix->yx;
241 major = sqrt (x: x*x + y*y);
242
243 if (major)
244 {
245 double det = matrix->xx * matrix->yy - matrix->yx * matrix->xy;
246
247 /*
248 * ignore mirroring
249 */
250 if (det < 0)
251 det = - det;
252
253 minor = det / major;
254 }
255 else
256 minor = 0.;
257 }
258
259 if (xscale)
260 *xscale = major;
261 if (yscale)
262 *yscale = minor;
263}
264
265/**
266 * pango_matrix_get_slant_ratio:
267 * @matrix: a `PangoMatrix`
268 *
269 * Gets the slant ratio of a matrix.
270 *
271 * For a simple shear matrix in the form:
272 *
273 * 1 λ
274 * 0 1
275 *
276 * this is simply λ.
277 *
278 * Returns: the slant ratio of @matrix
279 *
280 * Since: 1.50
281 */
282double
283pango_matrix_get_slant_ratio (const PangoMatrix *matrix)
284{
285 double x0, y0;
286 double x1, y1;
287
288 x0 = 0;
289 y0 = 1;
290 pango_matrix_transform_distance (matrix, dx: &x0, dy: &y0);
291
292 x1 = 1;
293 y1 = 0;
294 pango_matrix_transform_distance (matrix, dx: &x1, dy: &y1);
295
296 return (x0 * x1 + y0 * y1) / (x0 * x0 + y0 * y0);
297}
298
299/**
300 * pango_matrix_transform_distance:
301 * @matrix: (nullable): a `PangoMatrix`
302 * @dx: (inout): in/out X component of a distance vector
303 * @dy: (inout): in/out Y component of a distance vector
304 *
305 * Transforms the distance vector (@dx,@dy) by @matrix.
306 *
307 * This is similar to [method@Pango.Matrix.transform_point],
308 * except that the translation components of the transformation
309 * are ignored. The calculation of the returned vector is as follows:
310 *
311 * ```
312 * dx2 = dx1 * xx + dy1 * xy;
313 * dy2 = dx1 * yx + dy1 * yy;
314 * ```
315 *
316 * Affine transformations are position invariant, so the same vector
317 * always transforms to the same vector. If (@x1,@y1) transforms
318 * to (@x2,@y2) then (@x1+@dx1,@y1+@dy1) will transform to
319 * (@x1+@dx2,@y1+@dy2) for all values of @x1 and @x2.
320 *
321 * Since: 1.16
322 */
323void
324pango_matrix_transform_distance (const PangoMatrix *matrix,
325 double *dx,
326 double *dy)
327{
328 if (matrix)
329 {
330 double new_x, new_y;
331
332 new_x = (matrix->xx * *dx + matrix->xy * *dy);
333 new_y = (matrix->yx * *dx + matrix->yy * *dy);
334
335 *dx = new_x;
336 *dy = new_y;
337 }
338}
339
340/**
341 * pango_matrix_transform_point:
342 * @matrix: (nullable): a `PangoMatrix`
343 * @x: (inout): in/out X position
344 * @y: (inout): in/out Y position
345 *
346 * Transforms the point (@x, @y) by @matrix.
347 *
348 * Since: 1.16
349 **/
350void
351pango_matrix_transform_point (const PangoMatrix *matrix,
352 double *x,
353 double *y)
354{
355 if (matrix)
356 {
357 pango_matrix_transform_distance (matrix, dx: x, dy: y);
358
359 *x += matrix->x0;
360 *y += matrix->y0;
361 }
362}
363
364/**
365 * pango_matrix_transform_rectangle:
366 * @matrix: (nullable): a `PangoMatrix`
367 * @rect: (inout) (optional): in/out bounding box in Pango units
368 *
369 * First transforms @rect using @matrix, then calculates the bounding box
370 * of the transformed rectangle.
371 *
372 * This function is useful for example when you want to draw a rotated
373 * @PangoLayout to an image buffer, and want to know how large the image
374 * should be and how much you should shift the layout when rendering.
375 *
376 * If you have a rectangle in device units (pixels), use
377 * [method@Pango.Matrix.transform_pixel_rectangle].
378 *
379 * If you have the rectangle in Pango units and want to convert to
380 * transformed pixel bounding box, it is more accurate to transform it first
381 * (using this function) and pass the result to pango_extents_to_pixels(),
382 * first argument, for an inclusive rounded rectangle.
383 * However, there are valid reasons that you may want to convert
384 * to pixels first and then transform, for example when the transformed
385 * coordinates may overflow in Pango units (large matrix translation for
386 * example).
387 *
388 * Since: 1.16
389 */
390void
391pango_matrix_transform_rectangle (const PangoMatrix *matrix,
392 PangoRectangle *rect)
393{
394 int i;
395 double quad_x[4], quad_y[4];
396 double dx1, dy1;
397 double dx2, dy2;
398 double min_x, max_x;
399 double min_y, max_y;
400
401 if (!rect || !matrix)
402 return;
403
404 quad_x[0] = pango_units_to_double (i: rect->x);
405 quad_y[0] = pango_units_to_double (i: rect->y);
406 pango_matrix_transform_point (matrix, x: &quad_x[0], y: &quad_y[0]);
407
408 dx1 = pango_units_to_double (i: rect->width);
409 dy1 = 0;
410 pango_matrix_transform_distance (matrix, dx: &dx1, dy: &dy1);
411 quad_x[1] = quad_x[0] + dx1;
412 quad_y[1] = quad_y[0] + dy1;
413
414 dx2 = 0;
415 dy2 = pango_units_to_double (i: rect->height);
416 pango_matrix_transform_distance (matrix, dx: &dx2, dy: &dy2);
417 quad_x[2] = quad_x[0] + dx2;
418 quad_y[2] = quad_y[0] + dy2;
419
420 quad_x[3] = quad_x[0] + dx1 + dx2;
421 quad_y[3] = quad_y[0] + dy1 + dy2;
422
423 min_x = max_x = quad_x[0];
424 min_y = max_y = quad_y[0];
425
426 for (i=1; i < 4; i++) {
427 if (quad_x[i] < min_x)
428 min_x = quad_x[i];
429 else if (quad_x[i] > max_x)
430 max_x = quad_x[i];
431
432 if (quad_y[i] < min_y)
433 min_y = quad_y[i];
434 else if (quad_y[i] > max_y)
435 max_y = quad_y[i];
436 }
437
438 rect->x = pango_units_from_double (d: min_x);
439 rect->y = pango_units_from_double (d: min_y);
440 rect->width = pango_units_from_double (d: max_x) - rect->x;
441 rect->height = pango_units_from_double (d: max_y) - rect->y;
442}
443
444/**
445 * pango_matrix_transform_pixel_rectangle:
446 * @matrix: (nullable): a `PangoMatrix`
447 * @rect: (inout) (optional): in/out bounding box in device units
448 *
449 * First transforms the @rect using @matrix, then calculates the bounding box
450 * of the transformed rectangle.
451 *
452 * This function is useful for example when you want to draw a rotated
453 * @PangoLayout to an image buffer, and want to know how large the image
454 * should be and how much you should shift the layout when rendering.
455 *
456 * For better accuracy, you should use [method@Pango.Matrix.transform_rectangle]
457 * on original rectangle in Pango units and convert to pixels afterward
458 * using [func@extents_to_pixels]'s first argument.
459 *
460 * Since: 1.16
461 */
462void
463pango_matrix_transform_pixel_rectangle (const PangoMatrix *matrix,
464 PangoRectangle *rect)
465{
466 int i;
467 double quad_x[4], quad_y[4];
468 double dx1, dy1;
469 double dx2, dy2;
470 double min_x, max_x;
471 double min_y, max_y;
472
473 if (!rect || !matrix)
474 return;
475
476 quad_x[0] = rect->x;
477 quad_y[0] = rect->y;
478 pango_matrix_transform_point (matrix, x: &quad_x[0], y: &quad_y[0]);
479
480 dx1 = rect->width;
481 dy1 = 0;
482 pango_matrix_transform_distance (matrix, dx: &dx1, dy: &dy1);
483 quad_x[1] = quad_x[0] + dx1;
484 quad_y[1] = quad_y[0] + dy1;
485
486 dx2 = 0;
487 dy2 = rect->height;
488 pango_matrix_transform_distance (matrix, dx: &dx2, dy: &dy2);
489 quad_x[2] = quad_x[0] + dx2;
490 quad_y[2] = quad_y[0] + dy2;
491
492 quad_x[3] = quad_x[0] + dx1 + dx2;
493 quad_y[3] = quad_y[0] + dy1 + dy2;
494
495 min_x = max_x = quad_x[0];
496 min_y = max_y = quad_y[0];
497
498 for (i=1; i < 4; i++)
499 {
500 if (quad_x[i] < min_x)
501 min_x = quad_x[i];
502 else if (quad_x[i] > max_x)
503 max_x = quad_x[i];
504
505 if (quad_y[i] < min_y)
506 min_y = quad_y[i];
507 else if (quad_y[i] > max_y)
508 max_y = quad_y[i];
509 }
510
511 rect->x = floor (x: min_x);
512 rect->y = floor (x: min_y);
513 rect->width = ceil (x: max_x - rect->x);
514 rect->height = ceil (x: max_y - rect->y);
515}
516

source code of gtk/subprojects/pango/pango/pango-matrix.c