aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/area/CTM.java
blob: 07c3bbc0271a7f7683a7bbf96b44cd3851f7f1d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

package org.apache.fop.area;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;

import org.apache.fop.datatypes.FODimension;

import static org.apache.fop.fo.Constants.EN_LR_TB;
import static org.apache.fop.fo.Constants.EN_RL_TB;
import static org.apache.fop.fo.Constants.EN_TB_RL;

/**
 * Describe a PDF or PostScript style coordinate transformation matrix (CTM).
 * The matrix encodes translations, scaling and rotations of the coordinate
 * system used to render pages.
 */
public class CTM implements Serializable {

    private static final long serialVersionUID = -8743287485623778341L;

    private double a, b, c, d, e, f;

    private static final CTM CTM_LRTB = new CTM(1, 0, 0, 1, 0, 0);
    private static final CTM CTM_RLTB = new CTM(-1, 0, 0, 1, 0, 0);
    private static final CTM CTM_TBRL = new CTM(0, 1, -1, 0, 0, 0);

    /**
     * Create the identity matrix
     */
    public CTM() {
        a = 1;
        b = 0;
        c = 0;
        d = 1;
        e = 0;
        f = 0;
    }

    /**
     * Initialize a CTM from the passed arguments.
     *
     * @param a the x scale
     * @param b the x shear
     * @param c the y shear
     * @param d the y scale
     * @param e the x shift
     * @param f the y shift
     */
    public CTM(double a, double b, double c, double d, double e, double f) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        this.e = e;
        this.f = f;
    }

    /**
     * Initialize a CTM to the identity matrix with a translation
     * specified by x and y
     *
     * @param x the x shift
     * @param y the y shift.
     */
    public CTM(double x, double y) {
        this.a = 1;
        this.b = 0;
        this.c = 0;
        this.d = 1;
        this.e = x;
        this.f = y;
    }

    /**
     * Initialize a CTM with the values of another CTM.
     *
     * @param ctm another CTM
     */
    protected CTM(CTM ctm) {
        this.a = ctm.a;
        this.b = ctm.b;
        this.c = ctm.c;
        this.d = ctm.d;
        this.e = ctm.e;
        this.f = ctm.f;
    }

    /**
     * Initialize a CTM with the values of an AffineTransform.
     *
     * @param at the transformation matrix
     */
    public CTM(AffineTransform at) {
        double[] matrix = new double[6];
        at.getMatrix(matrix);
        this.a = matrix[0];
        this.b = matrix[1];
        this.c = matrix[2];
        this.d = matrix[3];
        this.e = matrix[4];
        this.f = matrix[5];
    }

    /**
     * Return a CTM which will transform coordinates for a particular writing-mode
     * into normalized first quandrant coordinates.
     * @param wm A writing mode constant from fo.properties.WritingMode, ie.
     * one of LR_TB, RL_TB, TB_RL.
     * @param ipd The inline-progression dimension of the reference area whose
     * CTM is being set..
     * @param bpd The block-progression dimension of the reference area whose
     * CTM is being set.
     * @return a new CTM with the required transform
     */
    public static CTM getWMctm(int wm, int ipd, int bpd) {
        CTM wmctm;
        switch (wm) {
            case EN_LR_TB:
                return new CTM(CTM_LRTB);
            case EN_RL_TB:
                wmctm = new CTM(CTM_RLTB);
                wmctm.e = ipd;
                return wmctm;
                //return  CTM_RLTB.translate(ipd, 0);
            case EN_TB_RL:  // CJK
                wmctm = new CTM(CTM_TBRL);
                wmctm.e = bpd;
                return wmctm;
                //return CTM_TBRL.translate(0, ipd);
            default:
                return null;
        }
    }

    /**
     * Multiply new passed CTM with this one and generate a new result CTM.
     * @param premult The CTM to multiply with this one. The new one will be
     * the first multiplicand.
     * @return CTM The result of multiplying premult * this.
     */
    public CTM multiply(CTM premult) {
        return new CTM ((premult.a * a) + (premult.b * c),
                              (premult.a * b) + (premult.b * d),
                              (premult.c * a) + (premult.d * c),
                              (premult.c * b) + (premult.d * d),
                              (premult.e * a) + (premult.f * c) + e,
                              (premult.e * b) + (premult.f * d) + f);
    }

    /**
     * Rotate this CTM by "angle" radians and return a new result CTM.
     * This is used to account for reference-orientation.
     * @param angle The angle in radians. Positive angles are measured counter-
     * clockwise.
     * @return CTM The result of rotating this CTM.
     */
    public CTM rotate(double angle) {
        double cos, sin;
        if (angle == 90.0 || angle == -270.0) {
            cos = 0.0;
            sin = 1.0;
        } else if (angle == 270.0 || angle == -90.0) {
            cos = 0.0;
            sin = -1.0;
        } else if (angle == 180.0 || angle == -180.0) {
            cos = -1.0;
            sin = 0.0;
        } else {
            double rad = Math.toRadians(angle);
            cos = Math.cos(rad);
            sin = Math.sin(rad);
        }
        CTM rotate = new CTM(cos, -sin, sin, cos, 0, 0);
        return multiply(rotate);
    }

    /**
     * Translate this CTM by the passed x and y values and return a new result CTM.
     * @param x The amount to translate along the x axis.
     * @param y The amount to translate along the y axis.
     * @return CTM The result of translating this CTM.
     */
    public CTM translate(double x, double y) {
        CTM translate = new CTM(1, 0, 0, 1, x, y);
        return multiply(translate);
    }

    /**
     * Scale this CTM by the passed x and y values and return a new result CTM.
     * @param x The amount to scale along the x axis.
     * @param y The amount to scale along the y axis.
     * @return CTM The result of scaling this CTM.
     */
    public CTM scale(double x, double y) {
        CTM scale = new CTM(x, 0, 0, y, 0, 0);
        return multiply(scale);
    }

    /**
     * Transform a rectangle by the CTM to produce a rectangle in the transformed
     * coordinate system.
     * @param inRect The rectangle in the original coordinate system
     * @return Rectangle2D The rectangle in the transformed coordinate system.
     */
    public Rectangle2D transform(Rectangle2D inRect) {
        // Store as 2 sets of 2 points and transform those, then
        // recalculate the width and height
        int x1t = (int)(inRect.getX() * a + inRect.getY() * c + e);
        int y1t = (int)(inRect.getX() * b + inRect.getY() * d + f);
        int x2t = (int)((inRect.getX() + inRect.getWidth()) * a
                        + (inRect.getY() + inRect.getHeight()) * c + e);
        int y2t = (int)((inRect.getX() + inRect.getWidth()) * b
                        + (inRect.getY() + inRect.getHeight()) * d + f);
        // Normalize with x1 < x2
        if (x1t > x2t) {
            int tmp = x2t;
            x2t = x1t;
            x1t = tmp;
        }
        if (y1t > y2t) {
            int tmp = y2t;
            y2t = y1t;
            y1t = tmp;
        }
        return new Rectangle(x1t, y1t, x2t - x1t, y2t - y1t);
    }

    /**
     * Get string for this transform.
     *
     * @return a string with the transform values
     */
    @Override
    public String toString() {
        return "[" + a + " " + b + " " + c + " " + d + " " + e + " "
               + f + "]";
    }

    /**
     * Get an array containing the values of this transform.
     * This creates and returns a new transform with the values in it.
     *
     * @return an array containing the transform values
     */
    public double[] toArray() {
        return new double[]{a, b, c, d, e, f};
    }

    /**
     * Returns this CTM as an AffineTransform object.
     * @return the AffineTransform representation
     */
    public AffineTransform toAffineTransform() {
        return new AffineTransform(toArray());
    }

    /**
     * Construct a coordinate transformation matrix (CTM).
     * @param absRefOrient absolute reference orientation
     * @param writingMode the writing mode
     * @param absVPrect absolute viewpoint rectangle
     * @param reldims relative dimensions
     * @return CTM the coordinate transformation matrix (CTM)
     */
    public static CTM getCTMandRelDims(int absRefOrient,
                                       int writingMode,
                                       Rectangle2D absVPrect,
                                       FODimension reldims) {
        int width, height;
        // We will use the absolute reference-orientation to set up the CTM.
        // The value here is relative to its ancestor reference area.
        if (absRefOrient % 180 == 0) {
            width = (int) absVPrect.getWidth();
            height = (int) absVPrect.getHeight();
        } else {
            // invert width and height since top left are rotated by 90 (cl or ccl)
            height = (int) absVPrect.getWidth();
            width = (int) absVPrect.getHeight();
        }
        /* Set up the CTM for the content of this reference area.
         * This will transform region content coordinates in
         * writing-mode relative into absolute page-relative
         * which will then be translated based on the position of
         * the region viewport.
         * (Note: scrolling between region vp and ref area when
         * doing online content!)
         */
        CTM ctm = new CTM(absVPrect.getX(), absVPrect.getY());

        // First transform for rotation
        if (absRefOrient != 0) {
            // Rotation implies translation to keep the drawing area in the
            // first quadrant. Note: rotation is counter-clockwise
            switch (absRefOrient) {
                case 90:
                case -270:
                    ctm = ctm.translate(0, width); // width = absVPrect.height
                    break;
                case 180:
                case -180:
                    ctm = ctm.translate(width, height);
                    break;
                case 270:
                case -90:
                    ctm = ctm.translate(height, 0); // height = absVPrect.width
                    break;
                default:
                    throw new RuntimeException();
            }
            ctm = ctm.rotate(absRefOrient);
        }
        /* Since we've already put adjusted width and height values for the
         * top and left positions implied by the reference-orientation, we
         * can set ipd and bpd appropriately based on the writing mode.
         */

        if (writingMode == EN_LR_TB || writingMode == EN_RL_TB) {
            reldims.ipd = width;
            reldims.bpd = height;
        } else {
            reldims.ipd = height;
            reldims.bpd = width;
        }
        // Set a rectangle to be the writing-mode relative version???
        // Now transform for writing mode
        return ctm.multiply(CTM.getWMctm(writingMode, reldims.ipd, reldims.bpd));
    }

}