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 
29 G_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  */
43 PangoMatrix *
pango_matrix_copy(const PangoMatrix * matrix)44 pango_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  */
66 void
pango_matrix_free(PangoMatrix * matrix)67 pango_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  */
87 void
pango_matrix_translate(PangoMatrix * matrix,double tx,double ty)88 pango_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  */
111 void
pango_matrix_scale(PangoMatrix * matrix,double scale_x,double scale_y)112 pango_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  */
135 void
pango_matrix_rotate(PangoMatrix * matrix,double degrees)136 pango_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 (r);
146   c = cos (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, &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  */
169 void
pango_matrix_concat(PangoMatrix * matrix,const PangoMatrix * new_matrix)170 pango_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  */
202 double
pango_matrix_get_font_scale_factor(const PangoMatrix * matrix)203 pango_matrix_get_font_scale_factor (const PangoMatrix *matrix)
204 {
205   double yscale;
206   pango_matrix_get_font_scale_factors (matrix, NULL, &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  **/
226 void
pango_matrix_get_font_scale_factors(const PangoMatrix * matrix,double * xscale,double * yscale)227 pango_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 + 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_transform_distance:
267  * @matrix: (nullable): a `PangoMatrix`
268  * @dx: (inout): in/out X component of a distance vector
269  * @dy: (inout): in/out Y component of a distance vector
270  *
271  * Transforms the distance vector (@dx,@dy) by @matrix.
272  *
273  * This is similar to [method@Pango.Matrix.transform_point],
274  * except that the translation components of the transformation
275  * are ignored. The calculation of the returned vector is as follows:
276  *
277  * ```
278  * dx2 = dx1 * xx + dy1 * xy;
279  * dy2 = dx1 * yx + dy1 * yy;
280  * ```
281  *
282  * Affine transformations are position invariant, so the same vector
283  * always transforms to the same vector. If (@x1,@y1) transforms
284  * to (@x2,@y2) then (@x1+@dx1,@y1+@dy1) will transform to
285  * (@x1+@dx2,@y1+@dy2) for all values of @x1 and @x2.
286  *
287  * Since: 1.16
288  */
289 void
pango_matrix_transform_distance(const PangoMatrix * matrix,double * dx,double * dy)290 pango_matrix_transform_distance (const PangoMatrix *matrix,
291 				 double            *dx,
292 				 double            *dy)
293 {
294   if (matrix)
295     {
296       double new_x, new_y;
297 
298       new_x = (matrix->xx * *dx + matrix->xy * *dy);
299       new_y = (matrix->yx * *dx + matrix->yy * *dy);
300 
301       *dx = new_x;
302       *dy = new_y;
303     }
304 }
305 
306 /**
307  * pango_matrix_transform_point:
308  * @matrix: (nullable): a `PangoMatrix`
309  * @x: (inout): in/out X position
310  * @y: (inout): in/out Y position
311  *
312  * Transforms the point (@x, @y) by @matrix.
313  *
314  * Since: 1.16
315  **/
316 void
pango_matrix_transform_point(const PangoMatrix * matrix,double * x,double * y)317 pango_matrix_transform_point (const PangoMatrix *matrix,
318 			      double            *x,
319 			      double            *y)
320 {
321   if (matrix)
322     {
323       pango_matrix_transform_distance (matrix, x, y);
324 
325       *x += matrix->x0;
326       *y += matrix->y0;
327     }
328 }
329 
330 /**
331  * pango_matrix_transform_rectangle:
332  * @matrix: (nullable): a `PangoMatrix`
333  * @rect: (inout) (optional): in/out bounding box in Pango units
334  *
335  * First transforms @rect using @matrix, then calculates the bounding box
336  * of the transformed rectangle.
337  *
338  * This function is useful for example when you want to draw a rotated
339  * @PangoLayout to an image buffer, and want to know how large the image
340  * should be and how much you should shift the layout when rendering.
341  *
342  * If you have a rectangle in device units (pixels), use
343  * [method@Pango.Matrix.transform_pixel_rectangle].
344  *
345  * If you have the rectangle in Pango units and want to convert to
346  * transformed pixel bounding box, it is more accurate to transform it first
347  * (using this function) and pass the result to pango_extents_to_pixels(),
348  * first argument, for an inclusive rounded rectangle.
349  * However, there are valid reasons that you may want to convert
350  * to pixels first and then transform, for example when the transformed
351  * coordinates may overflow in Pango units (large matrix translation for
352  * example).
353  *
354  * Since: 1.16
355  */
356 void
pango_matrix_transform_rectangle(const PangoMatrix * matrix,PangoRectangle * rect)357 pango_matrix_transform_rectangle (const PangoMatrix *matrix,
358 				  PangoRectangle    *rect)
359 {
360   int i;
361   double quad_x[4], quad_y[4];
362   double dx1, dy1;
363   double dx2, dy2;
364   double min_x, max_x;
365   double min_y, max_y;
366 
367   if (!rect || !matrix)
368     return;
369 
370   quad_x[0] = pango_units_to_double (rect->x);
371   quad_y[0] = pango_units_to_double (rect->y);
372   pango_matrix_transform_point (matrix, &quad_x[0], &quad_y[0]);
373 
374   dx1 = pango_units_to_double (rect->width);
375   dy1 = 0;
376   pango_matrix_transform_distance (matrix, &dx1, &dy1);
377   quad_x[1] = quad_x[0] + dx1;
378   quad_y[1] = quad_y[0] + dy1;
379 
380   dx2 = 0;
381   dy2 = pango_units_to_double (rect->height);
382   pango_matrix_transform_distance (matrix, &dx2, &dy2);
383   quad_x[2] = quad_x[0] + dx2;
384   quad_y[2] = quad_y[0] + dy2;
385 
386   quad_x[3] = quad_x[0] + dx1 + dx2;
387   quad_y[3] = quad_y[0] + dy1 + dy2;
388 
389   min_x = max_x = quad_x[0];
390   min_y = max_y = quad_y[0];
391 
392   for (i=1; i < 4; i++) {
393       if (quad_x[i] < min_x)
394 	  min_x = quad_x[i];
395       else if (quad_x[i] > max_x)
396 	  max_x = quad_x[i];
397 
398       if (quad_y[i] < min_y)
399 	  min_y = quad_y[i];
400       else if (quad_y[i] > max_y)
401 	  max_y = quad_y[i];
402   }
403 
404   rect->x      = pango_units_from_double (min_x);
405   rect->y      = pango_units_from_double (min_y);
406   rect->width  = pango_units_from_double (max_x) - rect->x;
407   rect->height = pango_units_from_double (max_y) - rect->y;
408 }
409 
410 /**
411  * pango_matrix_transform_pixel_rectangle:
412  * @matrix: (nullable): a `PangoMatrix`
413  * @rect: (inout) (optional): in/out bounding box in device units
414  *
415  * First transforms the @rect using @matrix, then calculates the bounding box
416  * of the transformed rectangle.
417  *
418  * This function is useful for example when you want to draw a rotated
419  * @PangoLayout to an image buffer, and want to know how large the image
420  * should be and how much you should shift the layout when rendering.
421  *
422  * For better accuracy, you should use [method@Pango.Matrix.transform_rectangle]
423  * on original rectangle in Pango units and convert to pixels afterward
424  * using [func@extents_to_pixels]'s first argument.
425  *
426  * Since: 1.16
427  */
428 void
pango_matrix_transform_pixel_rectangle(const PangoMatrix * matrix,PangoRectangle * rect)429 pango_matrix_transform_pixel_rectangle (const PangoMatrix *matrix,
430 					PangoRectangle    *rect)
431 {
432   int i;
433   double quad_x[4], quad_y[4];
434   double dx1, dy1;
435   double dx2, dy2;
436   double min_x, max_x;
437   double min_y, max_y;
438 
439   if (!rect || !matrix)
440     return;
441 
442   quad_x[0] = rect->x;
443   quad_y[0] = rect->y;
444   pango_matrix_transform_point (matrix, &quad_x[0], &quad_y[0]);
445 
446   dx1 = rect->width;
447   dy1 = 0;
448   pango_matrix_transform_distance (matrix, &dx1, &dy1);
449   quad_x[1] = quad_x[0] + dx1;
450   quad_y[1] = quad_y[0] + dy1;
451 
452   dx2 = 0;
453   dy2 = rect->height;
454   pango_matrix_transform_distance (matrix, &dx2, &dy2);
455   quad_x[2] = quad_x[0] + dx2;
456   quad_y[2] = quad_y[0] + dy2;
457 
458   quad_x[3] = quad_x[0] + dx1 + dx2;
459   quad_y[3] = quad_y[0] + dy1 + dy2;
460 
461   min_x = max_x = quad_x[0];
462   min_y = max_y = quad_y[0];
463 
464   for (i=1; i < 4; i++)
465     {
466       if (quad_x[i] < min_x)
467         min_x = quad_x[i];
468       else if (quad_x[i] > max_x)
469         max_x = quad_x[i];
470 
471       if (quad_y[i] < min_y)
472         min_y = quad_y[i];
473       else if (quad_y[i] > max_y)
474         max_y = quad_y[i];
475     }
476 
477   rect->x      = floor (min_x);
478   rect->y      = floor (min_y);
479   rect->width  = ceil (max_x - rect->x);
480   rect->height = ceil (max_y - rect->y);
481 }
482