1use crate::*;
2
3pub struct BreakWhole<'a, E: Element>(pub &'a E);
4
5impl<'a, E: Element> Element for BreakWhole<'a, E> {
6 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
7 let layout = self.layout(
8 ctx.text_pieces_cache,
9 ctx.width,
10 ctx.first_height,
11 ctx.full_height,
12 );
13
14 match layout {
15 Layout::NoPreBreak => self.0.first_location_usage(ctx),
16 Layout::Other {
17 pre_break, size, ..
18 } => {
19 if pre_break {
20 FirstLocationUsage::WillSkip
21 } else if size.height.is_none() {
22 FirstLocationUsage::NoneHeight
23 } else {
24 FirstLocationUsage::WillUse
27 }
28 }
29 }
30 }
31
32 fn measure(&self, ctx: MeasureCtx) -> ElementSize {
33 if let Some(breakable) = ctx.breakable {
34 let layout = self.layout(
35 ctx.text_pieces_cache,
36 ctx.width,
37 ctx.first_height,
38 breakable.full_height,
39 );
40
41 match layout {
42 Layout::NoPreBreak => self.0.measure(MeasureCtx {
43 breakable: Some(breakable),
44 ..ctx
45 }),
46 Layout::Other {
47 pre_break,
48 break_count,
49 size,
50 } => {
51 *breakable.break_count = break_count;
52
53 if pre_break {
54 *breakable.break_count += 1;
55 }
56
57 size
58 }
59 }
60 } else {
61 self.0.measure(ctx)
62 }
63 }
64
65 fn draw(&self, ctx: DrawCtx) -> ElementSize {
66 if let Some(breakable) = ctx.breakable {
67 let layout = self.layout(
68 ctx.text_pieces_cache,
69 ctx.width,
70 ctx.first_height,
71 breakable.full_height,
72 );
73
74 if let Layout::Other {
75 pre_break: true, ..
76 } = layout
77 {
78 let location = (breakable.do_break)(ctx.pdf, 0, None);
79
80 self.0.draw(DrawCtx {
81 pdf: ctx.pdf,
82 text_pieces_cache: ctx.text_pieces_cache,
83 width: ctx.width,
84 location,
85 first_height: breakable.full_height,
86 preferred_height: None,
87 breakable: Some(BreakableDraw {
88 full_height: breakable.full_height,
89 preferred_height_break_count: 0,
90 do_break: &mut |pdf, location_idx, height| {
91 (breakable.do_break)(pdf, location_idx + 1, height)
92 },
93 }),
94 })
95 } else {
96 self.0.draw(DrawCtx {
97 breakable: Some(BreakableDraw {
98 preferred_height_break_count: 0,
99 ..breakable
100 }),
101 preferred_height: None,
102 ..ctx
103 })
104 }
105 } else {
106 self.0.draw(DrawCtx {
107 preferred_height: None,
108 ..ctx
109 })
110 }
111 }
112}
113
114enum Layout {
115 NoPreBreak,
118 Other {
119 pre_break: bool,
120 break_count: u32,
121 size: ElementSize,
122 },
123}
124
125impl<'a, E: Element> BreakWhole<'a, E> {
126 fn layout(
127 &self,
128 text_pieces_cache: &TextPiecesCache,
129 width: WidthConstraint,
130 first_height: f32,
131 full_height: f32,
132 ) -> Layout {
133 if first_height == full_height {
134 return Layout::NoPreBreak;
135 }
136
137 let mut break_count = 0;
138 let mut extra_location_min_height = None;
139
140 let size = self.0.measure(MeasureCtx {
141 text_pieces_cache,
142 width,
143 first_height: full_height,
144 breakable: Some(BreakableMeasure {
145 full_height,
146 break_count: &mut break_count,
147 extra_location_min_height: &mut extra_location_min_height,
148 }),
149 });
150
151 Layout::Other {
152 pre_break: break_count > 0 || size.height.is_some_and(|h| h > first_height),
153 break_count,
154 size,
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::test_utils::{
163 record_passes::{Break, BreakableDraw, DrawPass, RecordPasses},
164 *,
165 };
166
167 #[test]
168 fn test_unbreakable() {
169 let width = WidthConstraint {
170 max: 3.,
171 expand: false,
172 };
173 let first_height = 12.;
174 let pos = (2., 10.);
175
176 let element = BuildElement(|ctx, callback| {
177 let content = RecordPasses::new(FakeText {
178 lines: 3,
179 line_height: 5.,
180 width: 3.,
181 });
182
183 let element = BreakWhole(&content);
184
185 let ret = callback.call(element);
186
187 content.assert_first_location_usage_count(0);
188
189 match ctx.pass {
190 build_element::Pass::FirstLocationUsage { .. } => unreachable!(),
191 build_element::Pass::Measure { .. } => {
192 content.assert_measure_count(1);
193 }
194 build_element::Pass::Draw { .. } => {
195 content.assert_measure_count(0);
196 content.assert_draw(DrawPass {
197 width,
198 first_height,
199 preferred_height: None,
200 page: 0,
201 layer: 0,
202 pos,
203 breakable: None,
204 });
205 }
206 }
207
208 ret
209 });
210
211 let output =
212 test_measure_draw_compatibility(&element, width, first_height, None, pos, (1., 1.));
213
214 output.assert_size(ElementSize {
215 width: Some(3.),
216 height: Some(15.),
217 });
218 }
219
220 #[test]
221 fn test_no_break() {
222 let width = WidthConstraint {
223 max: 3.,
224 expand: false,
225 };
226 let first_height = 12.;
227 let full_height = 20.;
228 let pos = (2., 10.);
229
230 let element = BuildElement(|ctx, callback| {
231 let content = RecordPasses::new(FakeText {
232 lines: 2,
233 line_height: 5.,
234 width: 3.,
235 });
236
237 let element = BreakWhole(&content);
238
239 let ret = callback.call(element);
240
241 content.assert_measure_count(1);
242 content.assert_first_location_usage_count(0);
243
244 match ctx.pass {
245 build_element::Pass::FirstLocationUsage { .. } => unreachable!(),
246 build_element::Pass::Measure { .. } => {}
247 build_element::Pass::Draw { .. } => {
248 content.assert_draw(DrawPass {
249 width,
250 first_height,
251 preferred_height: None,
252 page: 0,
253 layer: 0,
254 pos,
255 breakable: Some(BreakableDraw {
256 full_height,
257 preferred_height_break_count: 0,
258 breaks: vec![],
259 }),
260 });
261 }
262 }
263
264 ret
265 });
266
267 let output = test_measure_draw_compatibility(
268 &element,
269 width,
270 first_height,
271 Some(full_height),
272 pos,
273 (1., 1.),
274 );
275
276 output.assert_size(ElementSize {
277 width: Some(3.),
278 height: Some(10.),
279 });
280 output.breakable.unwrap().assert_break_count(0);
281 }
282
283 #[test]
284 fn test_break() {
285 let width = WidthConstraint {
286 max: 3.,
287 expand: false,
288 };
289 let first_height = 12.;
290 let full_height = 20.;
291 let pos = (2., 10.);
292
293 let element = BuildElement(|ctx, callback| {
294 let content = RecordPasses::new(FakeText {
295 lines: 3,
296 line_height: 5.,
297 width: 3.,
298 });
299
300 let element = BreakWhole(&content);
301
302 let ret = callback.call(element);
303
304 content.assert_measure_count(1);
305 content.assert_first_location_usage_count(0);
306
307 match ctx.pass {
308 build_element::Pass::FirstLocationUsage { .. } => unreachable!(),
309 build_element::Pass::Measure { .. } => {}
310 build_element::Pass::Draw { .. } => {
311 content.assert_draw(DrawPass {
312 width,
313 first_height: full_height,
314 preferred_height: None,
315 page: 1,
316 layer: 0,
317 pos,
318 breakable: Some(BreakableDraw {
319 full_height,
320 preferred_height_break_count: 0,
321 breaks: vec![],
322 }),
323 });
324 }
325 }
326
327 ret
328 });
329
330 let output = test_measure_draw_compatibility(
331 &element,
332 width,
333 first_height,
334 Some(full_height),
335 pos,
336 (1., 1.),
337 );
338
339 output.assert_size(ElementSize {
340 width: Some(3.),
341 height: Some(15.),
342 });
343 output.breakable.unwrap().assert_break_count(1);
344 }
345
346 #[test]
347 fn test_unhelpful_break() {
348 let width = WidthConstraint {
349 max: 3.,
350 expand: false,
351 };
352 let first_height = 14.;
353 let full_height = 14.;
354 let pos = (2., 10.);
355
356 let element = BuildElement(|ctx, callback| {
357 let content = RecordPasses::new(FakeText {
358 lines: 3,
359 line_height: 5.,
360 width: 3.,
361 });
362
363 let element = BreakWhole(&content);
364
365 let ret = callback.call(element);
366
367 content.assert_first_location_usage_count(0);
368
369 match ctx.pass {
370 build_element::Pass::FirstLocationUsage { .. } => unreachable!(),
371 build_element::Pass::Measure { .. } => {
372 content.assert_measure_count(1);
373 }
374 build_element::Pass::Draw { .. } => {
375 content.assert_measure_count(0);
376 content.assert_draw(DrawPass {
377 width,
378 first_height: full_height,
379 preferred_height: None,
380 page: 0,
381 layer: 0,
382 pos,
383 breakable: Some(BreakableDraw {
384 full_height,
385 preferred_height_break_count: 0,
386 breaks: vec![Break {
387 page: 1,
388 layer: 0,
389 pos,
390 }],
391 }),
392 });
393 }
394 }
395
396 ret
397 });
398
399 let output = test_measure_draw_compatibility(
400 &element,
401 width,
402 first_height,
403 Some(full_height),
404 pos,
405 (1., 1.),
406 );
407
408 output.assert_size(ElementSize {
409 width: Some(3.),
410 height: Some(5.),
411 });
412 output.breakable.unwrap().assert_break_count(1);
413 }
414}