@@ -66,4 +66,130 @@ public function testLayoutUsesBreakNodesToMoveToTheNextLine(): void
6666 self ::assertSame ('Second line ' , $ result ->lines [1 ]->text );
6767 self ::assertLessThan ($ result ->lines [0 ]->y , $ result ->lines [1 ]->y );
6868 }
69+
70+ public function testLayoutSupportsRightAndCenterAlignmentWithFallbackWidth (): void
71+ {
72+ $ engine = new LinearLayoutEngine ();
73+
74+ $ result = $ engine ->layout ([
75+ new Node (
76+ tag: 'p ' ,
77+ text: 'Right ' ,
78+ attributes: [
79+ 'style ' => 'text-align:right;margin:1 2 3 4;padding:5 6 7 8;width:0;font-size:10 ' ,
80+ ],
81+ ),
82+ new Node (
83+ tag: 'p ' ,
84+ text: 'Center ' ,
85+ attributes: [
86+ 'style ' => 'text-align:center;margin:1 2 3 4;padding:5 6 7 8;width:0;font-size:10 ' ,
87+ ],
88+ ),
89+ ], 100.0 , 100.0 );
90+
91+ self ::assertCount (2 , $ result ->lines );
92+ self ::assertSame ('Right ' , $ result ->lines [0 ]->text );
93+ self ::assertSame ('Center ' , $ result ->lines [1 ]->text );
94+ self ::assertEqualsWithDelta (84.0 , $ result ->lines [0 ]->x , 0.0001 );
95+ self ::assertEqualsWithDelta (52.0 , $ result ->lines [1 ]->x , 0.0001 );
96+ self ::assertEqualsWithDelta (82.0 , $ result ->lines [0 ]->y , 0.0001 );
97+ }
98+
99+ public function testLayoutTreatsNonPositiveImageDimensionsAsDefaultsAndClampsToCanvas (): void
100+ {
101+ $ engine = new LinearLayoutEngine ();
102+
103+ $ result = $ engine ->layout ([
104+ new Node (
105+ tag: 'img ' ,
106+ text: '' ,
107+ attributes: [
108+ 'src ' => '/fixture/signature.png ' ,
109+ 'style ' => 'width:0;height:-10 ' ,
110+ ],
111+ ),
112+ ], 20.0 , 15.0 );
113+
114+ self ::assertCount (1 , $ result ->images );
115+ self ::assertSame ('/fixture/signature.png ' , $ result ->images [0 ]->source );
116+ self ::assertSame ('Im0 ' , $ result ->images [0 ]->alias );
117+ self ::assertEqualsWithDelta (20.0 , $ result ->images [0 ]->width , 0.0001 );
118+ self ::assertEqualsWithDelta (15.0 , $ result ->images [0 ]->height , 0.0001 );
119+ self ::assertEqualsWithDelta (0.0 , $ result ->images [0 ]->y , 0.0001 );
120+ }
121+
122+ public function testLayoutResolvesQuotedFontFamilyAndNumericBoldWeight (): void
123+ {
124+ $ engine = new LinearLayoutEngine ();
125+
126+ $ result = $ engine ->layout ([
127+ new Node (
128+ tag: 'p ' ,
129+ text: 'Times bold ' ,
130+ attributes: ['style ' => 'font-family: "Times New Roman" ; font-weight:600; font-size:10 ' ],
131+ ),
132+ new Node (
133+ tag: 'p ' ,
134+ text: 'Helvetica regular ' ,
135+ attributes: ['style ' => 'font-family: Helvetica; font-weight:599; font-size:10 ' ],
136+ ),
137+ ], 120.0 , 90.0 );
138+
139+ self ::assertCount (2 , $ result ->lines );
140+ self ::assertSame ('F4 ' , $ result ->lines [0 ]->fontAlias );
141+ self ::assertSame ('F1 ' , $ result ->lines [1 ]->fontAlias );
142+ }
143+
144+ public function testLayoutImageFlowAdvancesCursorAndAliasForSubsequentNodes (): void
145+ {
146+ $ engine = new LinearLayoutEngine ();
147+
148+ $ result = $ engine ->layout ([
149+ new Node (tag: 'img ' , text: '' , attributes: ['src ' => '/a.png ' , 'style ' => 'width:10px;height:8px;margin:0 0 3 0;padding:0 0 4 0 ' ]),
150+ new Node (tag: 'img ' , text: '' , attributes: ['src ' => '/b.png ' , 'style ' => 'width:10px;height:8px;margin:0 0 3 0;padding:0 0 4 0 ' ]),
151+ new Node (tag: 'p ' , text: 'After images ' , attributes: ['style ' => 'font-size:10 ' ]),
152+ ], 100.0 , 100.0 );
153+
154+ self ::assertCount (2 , $ result ->images );
155+ self ::assertSame ('Im0 ' , $ result ->images [0 ]->alias );
156+ self ::assertSame ('Im1 ' , $ result ->images [1 ]->alias );
157+ self ::assertEqualsWithDelta (82.0 , $ result ->images [0 ]->y , 0.0001 );
158+ self ::assertEqualsWithDelta (67.0 , $ result ->images [1 ]->y , 0.0001 );
159+ self ::assertCount (1 , $ result ->lines );
160+ self ::assertSame ('After images ' , $ result ->lines [0 ]->text );
161+ self ::assertEqualsWithDelta (58.0 , $ result ->lines [0 ]->y , 0.0001 );
162+ }
163+
164+ public function testLayoutUsesCssSpacingShorthandSemanticsForTwoThreeAndFourValues (): void
165+ {
166+ $ engine = new LinearLayoutEngine ();
167+
168+ $ result = $ engine ->layout ([
169+ new Node (tag: 'p ' , text: 'Two ' , attributes: ['style ' => 'font-size:10;margin:1 2 ' ]),
170+ new Node (tag: 'p ' , text: 'Three ' , attributes: ['style ' => 'font-size:10;margin:1 2 3 ' ]),
171+ new Node (tag: 'p ' , text: 'Four ' , attributes: ['style ' => 'font-size:10;margin:1 2 3 4 ' ]),
172+ ], 100.0 , 100.0 );
173+
174+ self ::assertCount (3 , $ result ->lines );
175+ self ::assertEqualsWithDelta (10.0 , $ result ->lines [0 ]->x , 0.0001 );
176+ self ::assertEqualsWithDelta (10.0 , $ result ->lines [1 ]->x , 0.0001 );
177+ self ::assertEqualsWithDelta (12.0 , $ result ->lines [2 ]->x , 0.0001 );
178+ self ::assertEqualsWithDelta (87.0 , $ result ->lines [0 ]->y , 0.0001 );
179+ self ::assertEqualsWithDelta (73.0 , $ result ->lines [1 ]->y , 0.0001 );
180+ self ::assertEqualsWithDelta (57.0 , $ result ->lines [2 ]->y , 0.0001 );
181+ }
182+
183+ public function testLayoutTreatsZeroImageHeightAsDefaultDimension (): void
184+ {
185+ $ engine = new LinearLayoutEngine ();
186+
187+ $ result = $ engine ->layout ([
188+ new Node (tag: 'img ' , text: '' , attributes: ['src ' => '/zero.png ' , 'style ' => 'width:12;height:0 ' ]),
189+ ], 200.0 , 120.0 );
190+
191+ self ::assertCount (1 , $ result ->images );
192+ self ::assertEqualsWithDelta (12.0 , $ result ->images [0 ]->width , 0.0001 );
193+ self ::assertEqualsWithDelta (32.0 , $ result ->images [0 ]->height , 0.0001 );
194+ }
69195}
0 commit comments