7
7
use Intervention \Image \Drivers \Vips \Core ;
8
8
use Intervention \Image \Drivers \Vips \FontProcessor ;
9
9
use Intervention \Image \Exceptions \RuntimeException ;
10
+ use Intervention \Image \Geometry \Factories \CircleFactory ;
11
+ use Intervention \Image \Geometry \Rectangle ;
10
12
use Intervention \Image \Interfaces \FrameInterface ;
11
13
use Intervention \Image \Interfaces \ImageInterface ;
14
+ use Intervention \Image \Interfaces \PointInterface ;
12
15
use Intervention \Image \Interfaces \SpecializedInterface ;
13
16
use Intervention \Image \Modifiers \TextModifier as GenericTextModifier ;
17
+ use Intervention \Image \Typography \TextBlock ;
14
18
use Jcupitt \Vips \BlendMode ;
15
19
use Jcupitt \Vips \Image as VipsImage ;
16
20
use Jcupitt \Vips \Exception as VipsException ;
@@ -26,87 +30,123 @@ class TextModifier extends GenericTextModifier implements SpecializedInterface
26
30
*/
27
31
public function apply (ImageInterface $ image ): ImageInterface
28
32
{
33
+ $ textBlock = new TextBlock ($ this ->text );
29
34
$ fontProcessor = new FontProcessor ();
35
+
36
+ // decode text color
30
37
$ color = $ this ->driver ()->handleInput ($ this ->font ->color ());
31
- $ strokeColor = $ this ->driver ()->handleInput ($ this ->font ->strokeColor ());
32
- $ lines = $ fontProcessor ->textBlock ($ this ->text , $ this ->font , $ this ->position );
33
38
34
- foreach ($ lines as $ line ) {
35
- // build vips image from text
36
- $ text = $ fontProcessor ->textToVipsImage ((string ) $ line , $ this ->font , $ color );
39
+ // build vips image with text
40
+ $ textBlockImage = $ fontProcessor ->textToVipsImage ($ this ->text , $ this ->font , $ color );
41
+
42
+ // calculate block position
43
+ $ blockSize = $ this ->blockSize ($ textBlockImage );
44
+
45
+ // calculate baseline
46
+ $ capImage = $ fontProcessor ->textToVipsImage ('T ' , $ this ->font );
47
+ $ baseline = $ capImage ->height + $ capImage ->yoffset ;
48
+
49
+ // adjust block size
50
+ switch ($ this ->font ->valignment ()) {
51
+ case 'top ' :
52
+ $ blockSize ->movePointsY ($ baseline * -1 );
53
+ $ blockSize ->movePointsY ($ textBlockImage ->yoffset );
54
+ $ blockSize ->movePointsY ($ capImage ->height );
55
+ break ;
56
+
57
+ case 'bottom ' :
58
+ $ lastLineImage = $ fontProcessor ->textToVipsImage ((string ) $ textBlock ->last (), $ this ->font );
59
+ $ blockSize ->movePointsY ($ lastLineImage ->height );
60
+ $ blockSize ->movePointsY ($ baseline * -1 );
61
+ $ blockSize ->movePointsY ($ lastLineImage ->yoffset );
62
+ break ;
63
+ }
37
64
38
- // original line height from vips image before rotation
39
- $ height = $ text -> height ;
65
+ // apply rotation
66
+ $ blockSize -> rotate ( $ this -> font -> angle ()) ;
40
67
41
- // apply rotation
42
- $ text = $ this -> maybeRotateText ( $ text );
68
+ // extract block position
69
+ $ blockPosition = clone $ blockSize -> last ( );
43
70
44
- if ($ this ->font ->hasStrokeEffect ()) {
45
- // build stroke text image if applicable
46
- $ stroke = $ fontProcessor ->textToVipsImage ((string ) $ line , $ this ->font , $ strokeColor );
71
+ // apply text rotation if necessary
72
+ $ textBlockImage = $ this ->maybeRotateText ($ textBlockImage );
47
73
48
- // original line height from vips image before rotation
49
- $ strokeHeight = $ stroke ->height ;
74
+ // apply rotation offset to block position
75
+ if ($ this ->font ->angle () != 0 ) {
76
+ $ blockPosition ->move (
77
+ $ textBlockImage ->xoffset * -1 ,
78
+ $ textBlockImage ->yoffset * -1
79
+ );
80
+ }
50
81
51
- // apply rotation for stroke effect
52
- $ stroke = $ this -> maybeRotateText ( $ stroke );
53
- }
82
+ if ( $ this -> font -> hasStrokeEffect ()) {
83
+ // decode stroke color
84
+ $ strokeColor = $ this -> driver ()-> handleInput ( $ this -> font -> strokeColor ());
54
85
55
- if (!$ image ->isAnimated ()) {
56
- $ modified = $ image ->core ()->first ();
86
+ // build stroke text image if applicable
87
+ $ stroke = $ fontProcessor ->textToVipsImage ($ this ->text , $ this ->font , $ strokeColor );
88
+
89
+ // apply rotation for stroke effect
90
+ $ stroke = $ this ->maybeRotateText ($ stroke );
91
+ }
92
+
93
+ if (!$ image ->isAnimated ()) {
94
+ $ modified = $ image ->core ()->first ();
95
+
96
+ if (isset ($ stroke )) {
97
+ // draw stroke effect with offsets
98
+ foreach ($ this ->strokeOffsets ($ this ->font ) as $ offset ) {
99
+ $ modified = $ this ->placeTextOnFrame (
100
+ $ stroke ,
101
+ $ modified ,
102
+ $ blockPosition ->x () - $ offset ->x (),
103
+ $ blockPosition ->y () - $ offset ->y ()
104
+ );
105
+ }
106
+ }
57
107
58
- if (isset ($ stroke ) && isset ($ strokeHeight )) {
108
+ // place text image on original image
109
+ $ modified = $ this ->placeTextOnFrame (
110
+ $ textBlockImage ,
111
+ $ modified ,
112
+ $ blockPosition ->x (),
113
+ $ blockPosition ->y ()
114
+ );
115
+
116
+ $ modified = $ modified ->native ();
117
+ } else {
118
+ $ frames = [];
119
+ foreach ($ image as $ frame ) {
120
+ $ modifiedFrame = $ frame ;
121
+
122
+ if (isset ($ stroke )) {
59
123
// draw stroke effect with offsets
60
124
foreach ($ this ->strokeOffsets ($ this ->font ) as $ offset ) {
61
- $ modified = $ this ->placeTextOnFrame (
125
+ $ modifiedFrame = $ this ->placeTextOnFrame (
62
126
$ stroke ,
63
- $ modified ,
64
- $ line -> position () ->x () - $ offset ->x (),
65
- $ line -> position ()-> y () - $ strokeHeight - $ offset ->y (),
127
+ $ modifiedFrame ,
128
+ $ blockPosition ->x () - $ offset ->x (),
129
+ $ blockPosition -> y () - $ offset ->y ()
66
130
);
67
131
}
68
132
}
69
133
70
134
// place text image on original image
71
- $ modified = $ this ->placeTextOnFrame (
72
- $ text ,
73
- $ modified ,
74
- $ line -> position () ->x (),
75
- $ line -> position ()-> y () - $ height ,
135
+ $ modifiedFrame = $ this ->placeTextOnFrame (
136
+ $ textBlockImage ,
137
+ $ modifiedFrame ,
138
+ $ blockPosition ->x (),
139
+ $ blockPosition -> y ()
76
140
);
77
141
78
- $ modified = $ modified ->native ();
79
- } else {
80
- $ frames = [];
81
- foreach ($ image as $ frame ) {
82
- $ modifiedFrame = $ frame ;
83
- if (isset ($ stroke ) && isset ($ strokeHeight )) {
84
- // draw stroke effect with offsets
85
- foreach ($ this ->strokeOffsets ($ this ->font ) as $ offset ) {
86
- $ modifiedFrame = $ this ->placeTextOnFrame (
87
- $ stroke ,
88
- $ modifiedFrame ,
89
- $ line ->position ()->x () - $ offset ->x (),
90
- $ line ->position ()->y () - $ strokeHeight - $ offset ->y (),
91
- );
92
- }
93
- }
94
- // place text image on original image
95
- $ modifiedFrame = $ this ->placeTextOnFrame (
96
- $ text ,
97
- $ modifiedFrame ,
98
- $ line ->position ()->x (),
99
- $ line ->position ()->y () - $ height ,
100
- );
101
-
102
- $ frames [] = $ modifiedFrame ;
103
- }
104
-
105
- $ modified = Core::replaceFrames ($ image ->core ()->native (), $ frames );
142
+ $ frames [] = $ modifiedFrame ;
106
143
}
107
- $ image ->core ()->setNative ($ modified );
144
+
145
+ $ modified = Core::replaceFrames ($ image ->core ()->native (), $ frames );
108
146
}
109
147
148
+ $ image ->core ()->setNative ($ modified );
149
+
110
150
return $ image ;
111
151
}
112
152
@@ -128,12 +168,27 @@ private function placeTextOnFrame(VipsImage $text, FrameInterface $frame, int $x
128
168
return $ frame ;
129
169
}
130
170
171
+ /**
172
+ * Build size from given vips image
173
+ *
174
+ * @param VipsImage $blockImage
175
+ * @return Rectangle
176
+ */
177
+ private function blockSize (VipsImage $ blockImage ): Rectangle
178
+ {
179
+ $ imageSize = new Rectangle ($ blockImage ->width , $ blockImage ->height , $ this ->position );
180
+ $ imageSize ->align ($ this ->font ->alignment ());
181
+ $ imageSize ->valign ($ this ->font ->valignment ());
182
+
183
+ return $ imageSize ;
184
+ }
185
+
131
186
/**
132
187
* Maybe rotate text image according to current font angle
133
188
*
134
189
* @param VipsImage $text
135
- * @return VipsImage
136
190
* @throws VipsException
191
+ * @return VipsImage
137
192
*/
138
193
private function maybeRotateText (VipsImage $ text ): VipsImage
139
194
{
@@ -145,4 +200,31 @@ private function maybeRotateText(VipsImage $text): VipsImage
145
200
default => $ text ->similarity (['angle ' => $ this ->font ->angle ()]),
146
201
};
147
202
}
203
+
204
+ /** @phpstan-ignore method.unused */
205
+ private function debugPos (ImageInterface $ image , PointInterface $ position , Rectangle $ size ): void
206
+ {
207
+ // draw pos
208
+ // @phpstan-ignore missingType.checkedException
209
+ $ image ->drawCircle ($ position ->x (), $ position ->y (), function (CircleFactory $ circle ): void {
210
+ $ circle ->diameter (8 );
211
+ $ circle ->background ('red ' );
212
+ });
213
+
214
+ // draw points of size
215
+ foreach (array_chunk ($ size ->toArray (), 2 ) as $ point ) {
216
+ // @phpstan-ignore missingType.checkedException
217
+ $ image ->drawCircle ($ point [0 ], $ point [1 ], function (CircleFactory $ circle ): void {
218
+ $ circle ->diameter (12 );
219
+ $ circle ->border ('green ' );
220
+ });
221
+ }
222
+
223
+ // draw size's pivot
224
+ // @phpstan-ignore missingType.checkedException
225
+ $ image ->drawCircle ($ size ->pivot ()->x (), $ size ->pivot ()->y (), function (CircleFactory $ circle ): void {
226
+ $ circle ->diameter (20 );
227
+ $ circle ->border ('blue ' );
228
+ });
229
+ }
148
230
}
0 commit comments