Skip to content

Commit a5df2fd

Browse files
committed
feat: wrapping text around floating box
1 parent 16cf5bd commit a5df2fd

16 files changed

Lines changed: 727 additions & 32 deletions

File tree

.changeset/tiny-peas-grin.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@react-pdf/vite-example": minor
3+
"@react-pdf/stylesheet": minor
4+
"@react-pdf/layout": minor
5+
---
6+
7+
feat: wrapping text around image and view (float)
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
import React from 'react';
2+
import {
3+
Document,
4+
Page,
5+
View,
6+
Text,
7+
Image,
8+
StyleSheet,
9+
} from '@react-pdf/renderer';
10+
11+
import Quijote from '../../../public/quijote1.jpg';
12+
13+
const styles = StyleSheet.create({
14+
page: {
15+
padding: 40,
16+
},
17+
section: {
18+
marginBottom: 25,
19+
backgroundColor: '#f5f5f5',
20+
padding: 10,
21+
},
22+
sectionTitle: {
23+
fontSize: 24,
24+
fontWeight: 'bold',
25+
marginBottom: 10,
26+
color: '#333',
27+
},
28+
content: {
29+
backgroundColor: '#fff',
30+
},
31+
floatLeft: {
32+
float: 'left',
33+
width: 80,
34+
height: 80,
35+
backgroundColor: '#3498db',
36+
marginRight: 10,
37+
justifyContent: 'center',
38+
alignItems: 'center',
39+
},
40+
floatRight: {
41+
float: 'right',
42+
width: 80,
43+
height: 80,
44+
backgroundColor: '#e74c3c',
45+
marginLeft: 10,
46+
justifyContent: 'center',
47+
alignItems: 'center',
48+
},
49+
floatText: {
50+
color: 'white',
51+
fontSize: 18,
52+
},
53+
text: {
54+
fontSize: 12,
55+
color: '#333',
56+
},
57+
note: {
58+
fontSize: 8,
59+
color: '#666',
60+
marginTop: 20,
61+
},
62+
clearLeft: {
63+
clear: 'left',
64+
},
65+
clearRight: {
66+
clear: 'right',
67+
},
68+
clearBoth: {
69+
clear: 'both',
70+
},
71+
clearIndicator: {
72+
backgroundColor: '#2ecc71',
73+
padding: 5,
74+
marginTop: 5,
75+
},
76+
clearText: {
77+
fontSize: 10,
78+
color: '#fff',
79+
},
80+
});
81+
82+
const longText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.`;
83+
84+
const articleText = `The quick brown fox jumps over the lazy dog. This pangram contains every letter of the alphabet at least once. Typography and typesetting have long used this sentence to display fonts and test equipment. The phrase has been used since at least the late 19th century.
85+
86+
In the world of digital design, layout and composition remain fundamental skills. Understanding how text flows around images and other elements is crucial for creating professional documents. The float property, borrowed from CSS, allows designers to position elements while maintaining readable text flow.
87+
88+
Modern document generation requires flexibility in positioning elements. Whether creating reports, magazines, or books, the ability to wrap text around images and other content blocks enables rich, engaging layouts that capture readers' attention.
89+
90+
The art of typography extends beyond mere font selection. Line length, spacing, and the relationship between text and images all contribute to readability. When text wraps around floated elements, maintaining consistent line lengths becomes a consideration for optimal reading experience.
91+
92+
Professional publications often employ multiple float patterns within a single article. Left-aligned images might introduce a topic, while right-aligned callout boxes highlight key points. Center-aligned layouts with elements on both sides create visual interest and break up long passages of text.`;
93+
94+
const FloatExample = () => (
95+
<Document>
96+
{/* Magazine-style layout with left and right floats */}
97+
<Page size="A4" style={{ padding: 30 }}>
98+
<Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 15 }}>
99+
Magazine Article Layout
100+
</Text>
101+
102+
<View>
103+
{/* Left float - header image */}
104+
<Image
105+
src={Quijote}
106+
style={{
107+
float: 'left',
108+
width: 240,
109+
height: 160,
110+
marginRight: 10,
111+
}}
112+
/>
113+
114+
{/* Right float - callout box */}
115+
<View
116+
style={{
117+
float: 'right',
118+
width: 200,
119+
backgroundColor: '#e74c3c',
120+
padding: 20,
121+
marginTop: 300,
122+
marginLeft: 10,
123+
}}
124+
>
125+
<Text style={{ fontSize: 14, fontWeight: 'bold', color: '#fff' }}>
126+
Key Points
127+
</Text>
128+
<Text style={{ fontSize: 12, color: '#fff', marginTop: 8 }}>
129+
• Left float{'\n'}• Right float{'\n'}• Center float
130+
</Text>
131+
</View>
132+
133+
{/* Centered float - text flows on both sides */}
134+
{/* Content width = 535pt, element 200px, margin = (535-200)/2 ≈ 167 */}
135+
<View
136+
style={{
137+
float: 'left',
138+
width: 200,
139+
height: 160,
140+
marginLeft: 167,
141+
marginRight: 10,
142+
marginTop: 560,
143+
backgroundColor: '#9b59b6',
144+
justifyContent: 'center',
145+
alignItems: 'center',
146+
}}
147+
>
148+
<Text style={{ fontSize: 14, color: '#fff' }}>Centered</Text>
149+
</View>
150+
151+
{/* Single long text that wraps around both floats */}
152+
<Text style={{ fontSize: 15 }}>
153+
{articleText}
154+
{articleText}
155+
</Text>
156+
</View>
157+
158+
{/* New section with float - will appear on page 2 after pagination */}
159+
<View style={{ marginTop: 20 }}>
160+
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
161+
Continued Article
162+
</Text>
163+
<View>
164+
<View
165+
style={{
166+
float: 'left',
167+
width: 120,
168+
height: 100,
169+
backgroundColor: '#27ae60',
170+
marginRight: 10,
171+
justifyContent: 'center',
172+
alignItems: 'center',
173+
}}
174+
>
175+
<Text style={{ fontSize: 12, color: '#fff', textAlign: 'center' }}>
176+
NEW{'\n'}FLOAT
177+
</Text>
178+
</View>
179+
<Text style={{ fontSize: 15 }}>
180+
This section appears after the page break with a new float element.
181+
The text wraps around the green float box on the left. {articleText}
182+
</Text>
183+
</View>
184+
</View>
185+
</Page>
186+
187+
<Page size="A4" style={styles.page}>
188+
<View style={styles.section}>
189+
<Text style={styles.sectionTitle}>Left Float</Text>
190+
<View style={styles.content}>
191+
<View style={styles.floatLeft}>
192+
<Text style={styles.floatText}>FLOAT</Text>
193+
<Text style={styles.floatText}>LEFT</Text>
194+
</View>
195+
<Text style={styles.text}>
196+
{longText}
197+
{longText}
198+
</Text>
199+
</View>
200+
</View>
201+
202+
<View style={styles.section}>
203+
<Text style={styles.sectionTitle}>Right Float</Text>
204+
<View style={styles.content}>
205+
<View style={styles.floatRight}>
206+
<Text style={styles.floatText}>FLOAT</Text>
207+
<Text style={styles.floatText}>RIGHT</Text>
208+
</View>
209+
<Text style={styles.text}>
210+
{longText}
211+
{longText}
212+
</Text>
213+
</View>
214+
</View>
215+
216+
<View style={styles.section}>
217+
<Text style={styles.sectionTitle}>Multiple Floats</Text>
218+
<View style={styles.content}>
219+
<View style={[styles.floatLeft, { width: 60, height: 60 }]}>
220+
<Text style={styles.floatText}>L</Text>
221+
</View>
222+
<View style={[styles.floatRight, { width: 60, height: 60 }]}>
223+
<Text style={styles.floatText}>R</Text>
224+
</View>
225+
<Text style={styles.text}>
226+
{longText}
227+
{longText}
228+
</Text>
229+
</View>
230+
</View>
231+
232+
<View style={styles.section}>
233+
<Text style={styles.sectionTitle}>Image Float</Text>
234+
<View style={styles.content}>
235+
<Image
236+
src={Quijote}
237+
style={{
238+
float: 'left',
239+
width: 100,
240+
height: 100,
241+
marginRight: 10,
242+
}}
243+
/>
244+
<Text style={styles.text}>
245+
This example shows an image floated to the left with text wrapping
246+
around it. This is a common pattern for magazine-style layouts where
247+
images are placed alongside article text. {longText}
248+
</Text>
249+
</View>
250+
</View>
251+
252+
<Text style={styles.note}>
253+
Float property allows elements to be positioned to the left or right,
254+
with text wrapping around them. This is similar to CSS float behavior.
255+
</Text>
256+
</Page>
257+
258+
<Page size="A4" style={styles.page}>
259+
<View style={styles.section}>
260+
<Text style={styles.sectionTitle}>Clear: left (Left is taller)</Text>
261+
<View style={styles.content}>
262+
<View style={[styles.floatLeft, { height: 100 }]}>
263+
<Text style={styles.floatText}>LEFT</Text>
264+
<Text style={styles.floatText}>100px</Text>
265+
</View>
266+
<View style={[styles.floatRight, { height: 50 }]}>
267+
<Text style={styles.floatText}>RIGHT</Text>
268+
<Text style={styles.floatText}>50px</Text>
269+
</View>
270+
<Text style={styles.text}>
271+
This text wraps around both floats. The left float is 100px tall and
272+
the right float is only 50px tall.
273+
</Text>
274+
<View style={[styles.clearIndicator, { clear: 'left' }]}>
275+
<Text style={styles.clearText}>
276+
clear: left - This green box should be below the TALL left float
277+
(at 100px position)
278+
</Text>
279+
</View>
280+
<Text style={styles.text}>
281+
This text comes after clear:left. It should start below the left
282+
float (100px). Since the right float was only 50px, it ended above
283+
this position.
284+
</Text>
285+
</View>
286+
</View>
287+
288+
<View style={{ ...styles.section, marginTop: 80 }}>
289+
<Text style={styles.sectionTitle}>Clear: right (Right is taller)</Text>
290+
<View style={styles.content}>
291+
<View style={[styles.floatLeft, { height: 50 }]}>
292+
<Text style={styles.floatText}>LEFT</Text>
293+
<Text style={styles.floatText}>50px</Text>
294+
</View>
295+
<View style={[styles.floatRight, { height: 100 }]}>
296+
<Text style={styles.floatText}>RIGHT</Text>
297+
<Text style={styles.floatText}>100px</Text>
298+
</View>
299+
<Text style={styles.text}>
300+
This text wraps around both floats. The left float is only 50px tall
301+
and the right float is 100px tall.
302+
</Text>
303+
<View style={[styles.clearIndicator, { clear: 'right' }]}>
304+
<Text style={styles.clearText}>
305+
clear: right - This green box should be below the TALL right float
306+
(at 100px position)
307+
</Text>
308+
</View>
309+
<Text style={styles.text}>
310+
This text comes after clear:right. It should start below the right
311+
float (100px). Since the left float was only 50px, it ended above
312+
this position.
313+
</Text>
314+
</View>
315+
</View>
316+
317+
<View style={{ ...styles.section, marginTop: 80 }}>
318+
<Text style={styles.sectionTitle}>Clear: both</Text>
319+
<View style={styles.content}>
320+
<View style={[styles.floatLeft, { height: 60 }]}>
321+
<Text style={styles.floatText}>LEFT</Text>
322+
<Text style={styles.floatText}>60px</Text>
323+
</View>
324+
<View style={[styles.floatRight, { height: 90 }]}>
325+
<Text style={styles.floatText}>RIGHT</Text>
326+
<Text style={styles.floatText}>90px</Text>
327+
</View>
328+
<Text style={styles.text}>
329+
This text wraps around both floats. The left float is 60px and the
330+
right float is 90px tall.
331+
</Text>
332+
<View style={[styles.clearIndicator, { clear: 'both' }]}>
333+
<Text style={styles.clearText}>
334+
clear: both - This green box should be below BOTH floats (at 90px,
335+
the taller one)
336+
</Text>
337+
</View>
338+
<Text style={styles.text}>
339+
This text comes after clear:both. It should start below whichever
340+
float is taller (the right one at 90px in this case). Both floats
341+
have ended above this position.
342+
</Text>
343+
</View>
344+
</View>
345+
</Page>
346+
</Document>
347+
);
348+
349+
export default {
350+
id: 'float',
351+
name: 'Float (Text Wrapping)',
352+
description: 'Text wrapping around floated elements using float: left/right',
353+
Document: FloatExample,
354+
};

packages/examples/vite/src/examples/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import duplicatedImages from './duplicated-images';
22
import ellipsis from './ellipsis';
33
import emoji from './emoji';
4+
import float from './float';
45
import fontFamilyFallback from './font-family-fallback';
56
import fontWeight from './font-weight';
67
import fractals from './fractals';
@@ -25,6 +26,7 @@ const EXAMPLES = [
2526
duplicatedImages,
2627
ellipsis,
2728
emoji,
29+
float,
2830
fontFamilyFallback,
2931
fontWeight,
3032
fractals,

packages/layout/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import resolveYoga from './steps/resolveYoga';
55
import resolveZIndex from './steps/resolveZIndex';
66
import resolveAssets from './steps/resolveAssets';
77
import resolveStyles from './steps/resolveStyles';
8+
import resolveFloats from './steps/resolveFloats';
89
import resolveOrigins from './steps/resolveOrigins';
910
import resolveBookmarks from './steps/resolveBookmarks';
1011
import resolvePageSizes from './steps/resolvePageSizes';
@@ -23,6 +24,7 @@ const layout = asyncCompose(
2324
resolveAssets,
2425
resolvePagination,
2526
resolveTextLayout,
27+
resolveFloats,
2628
resolvePercentRadius,
2729
resolveDimensions,
2830
resolveSvg,

0 commit comments

Comments
 (0)