1 /*******************************************************
2
3 CoolReader Engine
4
5 lvrend.cpp: XML DOM tree rendering tools
6
7 (c) Vadim Lopatin, 2000-2006
8 This source code is distributed under the terms of
9 GNU General Public License
10 See LICENSE file for details
11
12 *******************************************************/
13
14 #include <stdlib.h>
15 #include <string.h>
16 #include "../include/lvtextfm.h"
17 #include "../include/lvtinydom.h"
18 #include "../include/fb2def.h"
19 #include "../include/lvrend.h"
20 #include "../include/crlog.h"
21
22 // Note about box model/sizing in crengine:
23 // https://quirksmode.org/css/user-interface/boxsizing.html says:
24 // - In the W3C box model, the width of an element gives the width of
25 // the content of the box, excluding padding and border.
26 // - In the traditional box model, the width of an element gives the
27 // width between the borders of the box, including padding and border.
28 // By default, all browsers use the W3C box model, with the exception
29 // of IE in "Quirks Mode" (IE5.5 Mode), which uses the traditional one.
30 //
31 // These models are toggable with CSS in current browsers:
32 // - the first one is used when "box-sizing: content-box" (default in browsers)
33 // - the second one is used when "box-sizing: border-box".
34 //
35 // crengine legacy rendering uses the traditional one (box-sizing: border-box).
36 // In enhanced rendering mode, the W3C box model can be enabled by
37 // setting BLOCK_RENDERING_USE_W3C_BOX_MODEL in flags.
38 //
39 // Note: internally in the code, RenderRectAccessor stores the position
40 // and width of the border box (and when in enhanced rendering mode, in
41 // its _inner* fields, those of the content box).
42 //
43 // See: https://www.456bereastreet.com/archive/201112/the_difference_between_widthauto_and_width100/
44 // for some example differences in rendering.
45 //
46 // As a side note, TABLE {width: auto} has a different behaviour that
47 // what is described there: with width:auto, a table adjusts its width to
48 // the table content, and does not take its full container width when
49 // the content does not need it.
50 // width: auto is the default for TABLEs in current browsers.
51 // crengine default used to be "width: 100%", but now that we
52 // can shrink to fit, it is "width: auto".
53
54 int gRenderDPI = DEF_RENDER_DPI; // if 0: old crengine behaviour: 1px/pt=1px, 1in/cm/pc...=0px
55 bool gRenderScaleFontWithDPI = DEF_RENDER_SCALE_FONT_WITH_DPI;
56 int gRootFontSize = 24; // will be reset as soon as font size is set
57
scaleForRenderDPI(int value)58 int scaleForRenderDPI( int value ) {
59 // if gRenderDPI == 0 or 96, use value as is (1px = 1px)
60 if (gRenderDPI && gRenderDPI != BASE_CSS_DPI) {
61 value = value * gRenderDPI / BASE_CSS_DPI;
62 }
63 return value;
64 }
65
66 // Uncomment for debugging enhanced block rendering
67 // #define DEBUG_BLOCK_RENDERING
68
69 //#define DEBUG_TREE_DRAW 3
70 // define to non-zero (1..5) to see block bounds
71 #define DEBUG_TREE_DRAW 0
72
73 //#ifdef _DEBUG
74 //#define DEBUG_DUMP_ENABLED
75 //#endif
76
77 #ifdef DEBUG_DUMP_ENABLED
78
79 class simpleLogFile
80 {
81 public:
82 FILE * f;
simpleLogFile(const char * fname)83 simpleLogFile(const char * fname) { f = fopen( fname, "wt" ); }
~simpleLogFile()84 ~simpleLogFile() { if (f) fclose(f); }
operator <<(const char * str)85 simpleLogFile & operator << ( const char * str ) { fprintf( f, "%s", str ); fflush( f ); return *this; }
86 //simpleLogFile & operator << ( int d ) { fprintf( f, "%d(0x%X) ", d, d ); fflush( f ); return *this; }
operator <<(int d)87 simpleLogFile & operator << ( int d ) { fprintf( f, "%d ", d ); fflush( f ); return *this; }
operator <<(const lChar32 * str)88 simpleLogFile & operator << ( const lChar32 * str )
89 {
90 if (str)
91 {
92 for (; *str; str++ )
93 {
94 fputc( *str >= 32 && *str<127 ? *str : '?', f );
95 }
96 }
97 fflush( f );
98 return *this;
99 }
operator <<(const lString32 & str)100 simpleLogFile & operator << ( const lString32 &str ) { return operator << (str.c_str()); }
101 };
102
103 simpleLogFile logfile("/tmp/logfile.log");
104
105 #else
106
107 // stubs
108 class simpleLogFile
109 {
110 public:
operator <<(const char *)111 simpleLogFile & operator << ( const char * ) { return *this; }
operator <<(int)112 simpleLogFile & operator << ( int ) { return *this; }
operator <<(const lChar32 *)113 simpleLogFile & operator << ( const lChar32 * ) { return *this; }
operator <<(const lString32 &)114 simpleLogFile & operator << ( const lString32 & ) { return *this; }
115 };
116
117 simpleLogFile logfile;
118
119 #endif
120
121 // prototypes
122 void copystyle( css_style_ref_t sourcestyle, css_style_ref_t deststyle );
123 css_page_break_t getPageBreakBefore( ldomNode * el );
124 int CssPageBreak2Flags( css_page_break_t prop );
125
126 ///////////////////////////////////////////////////////////////////////////////
127 //
128 // TABLE RENDERING CLASSES
129 //
130 ///////////////////////////////////////////////////////////////////////////////
131
132 // Uncomment for debugging table rendering:
133 // #define DEBUG_TABLE_RENDERING
134
135 #define TABLE_BORDER_WIDTH 1
136
137 class CCRTableCol;
138 class CCRTableRow;
139
140 class CCRTableCell {
141 public:
142 CCRTableCol * col;
143 CCRTableRow * row;
144 int col_index; // copy of col->index, only filled and used if RTL table for re-ordering cells
145 int direction;
146 int width;
147 int height;
148 int baseline;
149 int adjusted_baseline;
150 int percent;
151 int max_content_width;
152 int min_content_width;
153 short colspan;
154 short rowspan;
155 char halign;
156 char valign;
157 ldomNode * elem;
CCRTableCell()158 CCRTableCell() : col(NULL), row(NULL)
159 , direction(REND_DIRECTION_UNSET)
160 , width(0)
161 , height(0)
162 , baseline(0)
163 , adjusted_baseline(0)
164 , percent(0)
165 , max_content_width(0)
166 , min_content_width(0)
167 , colspan(1)
168 , rowspan(1)
169 , halign(0) // default to text-align: left
170 , valign(0) // default to vertical-align: baseline
171 , elem(NULL)
172 { }
173 };
174
175 class CCRTableRowGroup;
176
177 class CCRTableRow {
178 public:
179 int index;
180 int height;
181 int baseline;
182 int bottom_overflow; // extra height from row with rowspan>1
183 int y;
184 int numcols; // sum of colspan
185 int linkindex;
186 lString32Collection links;
187 ldomNode * elem;
188 LVPtrVector<CCRTableCell> cells;
189 CCRTableRowGroup * rowgroup;
190 LVRendPageContext * single_col_context; // we can add cells' lines instead of full rows
CCRTableRow()191 CCRTableRow() : index(0)
192 , height(0)
193 , baseline(0)
194 , bottom_overflow(0)
195 , y(0)
196 , numcols(0) // sum of colspan
197 , linkindex(-1)
198 , elem(NULL)
199 , rowgroup(NULL)
200 , single_col_context(NULL)
201 { }
202 };
203
204 class CCRTableRowGroup {
205 public:
206 int index;
207 int kind; // erm_table_header_group, erm_table_row_group or erm_table_footer_group
208 int height;
209 int y;
210 ldomNode * elem;
211 LVPtrVector<CCRTableRow, false> rows;
CCRTableRowGroup()212 CCRTableRowGroup() : index(0)
213 , height(0)
214 , y(0)
215 , elem(NULL)
216 { }
217 };
218
219 class CCRTableCol {
220 public:
221 int index;
222 int width;
223 int percent;
224 int max_width;
225 int sum_max_content_width;
226 int nb_sum_max_content_width;
227 int min_width;
228 int nrows;
229 int x; // sum of previous col widths
230 bool width_auto; // true when no width or percent is specified
231 // LVPtrVector<CCRTableCell, false> cells; // not used
232 ldomNode * elem;
CCRTableCol()233 CCRTableCol() :
234 index(0)
235 , width(0)
236 , percent(0)
237 , max_width(0)
238 , sum_max_content_width(0)
239 , nb_sum_max_content_width(0)
240 , min_width(0)
241 , nrows(0)
242 , x(0) // sum of previous col widths
243 , width_auto(true)
244 , elem( NULL )
245 { }
~CCRTableCol()246 ~CCRTableCol() { }
247 };
248
249 /*
250 in: string 25 35%
251 out: 25 -35
252 */
253 int StrToIntPercent( const lChar32 * s, int digitwidth=0 );
StrToIntPercent(const lChar32 * s,int digitwidth)254 int StrToIntPercent( const lChar32 * s, int digitwidth )
255 {
256 int n=0;
257 if (!s || !s[0]) return 0;
258 for (int i=0; s[i]; i++) {
259 if (s[i]>='0' && s[i]<='9') {
260 //=================
261 n=n*10+(s[i]-'0');
262 } else if (s[i]=='d') {
263 //=================
264 n=n*digitwidth;
265 break;
266 } else if (s[i]=='%') {
267 //=================
268 n=-n;
269 break;
270 }
271 }
272 return n;
273 }
274
275 // Utility function used in CCRTable::PlaceCells() when border_collapse.
276 // border_id is the border index: 0=top, 1=right, 2=bottom, 3=left.
277 // Update provided target_style and current_target_size.
278 // We don't implement fully the rules describe in:
279 // https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
280 //
281 // With use_neighbour_if_equal=true, neighbour border wins if its width
282 // is equal to target width.
283 // We would have intuitively set it to true, for a TABLE or TR border
284 // color to be used even if border width is the same as the TD one. But
285 // the above url rules state: "If border styles differ only in color,
286 // then a style set on a cell wins over one on a row, which wins over
287 // a row group, column, column group and, lastly, table."
collapse_border(css_style_ref_t & target_style,int & current_target_size,int border_id,ldomNode * neighbour_node,bool use_neighbour_if_equal=false)288 void collapse_border(css_style_ref_t & target_style, int & current_target_size,
289 int border_id, ldomNode * neighbour_node, bool use_neighbour_if_equal=false) {
290 if (neighbour_node) {
291 int neighbour_size = measureBorder(neighbour_node, border_id);
292 if ( neighbour_size > current_target_size ||
293 (use_neighbour_if_equal && neighbour_size == current_target_size) ) {
294 css_style_ref_t neighbour_style = neighbour_node->getStyle();
295 switch (border_id) {
296 case 0: target_style->border_style_top = neighbour_style->border_style_top; break;
297 case 1: target_style->border_style_right = neighbour_style->border_style_right; break;
298 case 2: target_style->border_style_bottom = neighbour_style->border_style_bottom; break;
299 case 3: target_style->border_style_left = neighbour_style->border_style_left; break;
300 }
301 target_style->border_width[border_id] = neighbour_style->border_width[border_id];
302 target_style->border_color[border_id] = neighbour_style->border_color[border_id];
303 current_target_size = neighbour_size;
304 }
305 }
306 }
307
308 class CCRTable {
309 public:
310 int table_width;
311 int digitwidth;
312 int direction;
313 bool is_rtl;
314 bool shrink_to_fit;
315 bool avoid_pb_inside;
316 bool enhanced_rendering;
317 bool is_ruby_table;
318 bool rows_rendering_reordered;
319 ldomNode * elem;
320 ldomNode * caption;
321 int caption_h;
322 int caption_direction;
323 LVPtrVector<CCRTableRow> rows;
324 LVPtrVector<CCRTableCol> cols;
325 LVPtrVector<CCRTableRowGroup> rowgroups;
326 // LVMatrix<CCRTableCell*> cells; // not used (it was filled, but never read)
327 CCRTableRowGroup * currentRowGroup;
328
ExtendCols(int ncols)329 void ExtendCols( int ncols ) {
330 while (cols.length()<ncols) {
331 CCRTableCol * col = new CCRTableCol;
332 col->index = cols.length();
333 cols.add(col);
334 }
335 }
336
LookupElem(ldomNode * el,int elem_direction,int state)337 int LookupElem( ldomNode * el, int elem_direction, int state ) {
338 if (!el->getChildCount())
339 return 0;
340 int colindex = 0;
341 for (int i=0; i<el->getChildCount(); i++) {
342 ldomNode * item = el->getChildElementNode(i);
343 if ( item ) {
344 // for each child element
345 css_style_ref_t style = item->getStyle();
346
347 int item_direction = elem_direction;
348 if ( item->hasAttribute( attr_dir ) ) {
349 lString32 dir = item->getAttributeValue( attr_dir );
350 dir = dir.lowercase();
351 if ( dir.compare("rtl") == 0 ) {
352 item_direction = REND_DIRECTION_RTL;
353 }
354 else if ( dir.compare("ltr") == 0 ) {
355 item_direction = REND_DIRECTION_LTR;
356 }
357 else if ( dir.compare("auto") == 0 ) {
358 item_direction = REND_DIRECTION_UNSET;
359 }
360 }
361 if ( style->direction != css_dir_inherit ) {
362 if ( style->direction == css_dir_rtl )
363 item_direction = REND_DIRECTION_RTL;
364 else if ( style->direction == css_dir_ltr )
365 item_direction = REND_DIRECTION_LTR;
366 else if ( style->direction == css_dir_unset )
367 item_direction = REND_DIRECTION_UNSET;
368 }
369
370 lvdom_element_render_method rendMethod = item->getRendMethod();
371 //CRLog::trace("LookupElem[%d] (%s, %d) %d", i, LCSTR(item->getNodeName()), state, (int)item->getRendMethod() );
372 switch ( rendMethod ) {
373 case erm_invisible: // invisible: don't render
374 // do nothing: invisible
375 break;
376 case erm_killed: // no room to render element, or unproper table element
377 {
378 // We won't visit this element in PlaceCells() and renderCells(),
379 // but we'll visit it in DrawDocument() as we walk the DOM tree.
380 // Give it some width and height so we can draw a symbol so users
381 // know there is some content missing.
382 RenderRectAccessor fmt = RenderRectAccessor( item );
383 fmt.setHeight( 15 ); // not squared, so it does not look
384 fmt.setWidth( 10 ); // like a list square bullet
385 fmt.setX( 0 ); // positioned at top left of its container
386 fmt.setY( 0 ); // (which ought to be a proper table element)
387 }
388 break;
389 case erm_table: // table element: render as table
390 // do nothing: impossible
391 break;
392 case erm_table_row_group: // table row group
393 case erm_table_header_group: // table header group
394 case erm_table_footer_group: // table footer group
395 if ( state==0 && currentRowGroup==NULL ) {
396 currentRowGroup = new CCRTableRowGroup();
397 currentRowGroup->elem = item;
398 currentRowGroup->index = rowgroups.length();
399 currentRowGroup->kind = rendMethod;
400 rowgroups.add( currentRowGroup );
401 LookupElem( item, item_direction, 0 );
402 currentRowGroup = NULL;
403 } else {
404 }
405 break;
406 case erm_table_column_group: // table column group
407 // just fall into groups
408 LookupElem( item, item_direction, 0 );
409 break;
410 case erm_table_row: // table row
411 {
412 // rows of table
413 CCRTableRow * row = new CCRTableRow;
414 row->elem = item;
415 if ( currentRowGroup ) {
416 // add row to group
417 row->rowgroup = currentRowGroup;
418 currentRowGroup->rows.add( row );
419 }
420 rows.add( row );
421 // What could <tr link="number"> have been in the past ?
422 // It's not mentioned in any HTML or FB2 spec,
423 // and row->linkindex is never used.
424 if (row->elem->hasAttribute(LXML_NS_ANY, attr_link)) {
425 lString32 lnk=row->elem->getAttributeValue(attr_link);
426 row->linkindex = lnk.atoi();
427 }
428 // recursion: search for inner elements
429 //int res =
430 LookupElem( item, item_direction, 1 ); // lookup row
431 }
432 break;
433 case erm_table_column: // table column
434 {
435 // cols width definitions
436 ExtendCols(colindex+1);
437 CCRTableCol * col = cols[colindex];
438 col->elem = item;
439 /*
440 lString32 w = item->getAttributeValue(attr_width);
441 if (!w.empty()) {
442 // TODO: px, em, and other length types support
443 int wn = StrToIntPercent(w.c_str(), digitwidth);
444 if (wn<0)
445 col->percent = -wn;
446 else if (wn>0)
447 col->width = wn;
448 }
449 */
450 css_length_t w = style->width;
451 if ( w.type == css_val_percent ) { // %
452 col->percent = w.value / 256;
453 }
454 else if ( w.type != css_val_unspecified ) { // px, em...
455 int em = item->getFont()->getSize();
456 col->width = lengthToPx( w, 0, em );
457 // (0 as the base width for %, as % was dealt with just above)
458 }
459 // otherwise cell->percent and cell->width stay at 0
460 colindex++;
461 }
462 break;
463 case erm_block: // render as block element (as containing other elements)
464 case erm_final: // final element: render the whole of its content as single text block
465 if ( style->display == css_d_table_caption ) {
466 caption = item;
467 caption_direction = item_direction;
468 }
469 else { // <th> or <td> inside <tr>
470 // Table cells became either erm_block or erm_final depending on their content
471
472 if ( rows.length()==0 ) {
473 CCRTableRow * row = new CCRTableRow;
474 row->elem = item;
475 if ( currentRowGroup ) {
476 // add row to group
477 row->rowgroup = currentRowGroup;
478 currentRowGroup->rows.add( row );
479 }
480 rows.add( row );
481 }
482
483
484 CCRTableCell * cell = new CCRTableCell;
485 cell->elem = item;
486 int cs=StrToIntPercent(item->getAttributeValue(attr_colspan).c_str());
487 if (cs>0 && cs<100) { // colspan=0 (span all remaining columns) not supported
488 cell->colspan=cs;
489 }
490 if ( is_ruby_table ) { // rbspan works just as colspan
491 int cs=StrToIntPercent(item->getAttributeValue(attr_rbspan).c_str());
492 if (cs>0 && cs<100) {
493 cell->colspan=cs;
494 }
495 }
496 int rs=StrToIntPercent(item->getAttributeValue(attr_rowspan).c_str());
497 if (rs>0 && rs<100) {
498 cell->rowspan=rs;
499 }
500 /*
501 // "width"
502 lString32 w = item->getAttributeValue(attr_width);
503 if (!w.empty()) {
504 int wn = StrToIntPercent(w.c_str(), digitwidth);
505 if (wn<0)
506 cell->percent = -wn;
507 else if (wn>0)
508 cell->width = wn;
509 }
510 // "align"
511 lString32 halign = item->getAttributeValue(attr_align);
512 if (halign == "center")
513 cell->halign = 1; // center
514 else if (halign == "right")
515 cell->halign = 2; // right
516 // "valign"
517 lString32 valign = item->getAttributeValue(attr_valign);
518 if (valign == "center")
519 cell->valign = 2; // middle
520 else if (valign == "bottom")
521 cell->valign = 3; // bottom
522 */
523 // These commented above attributes have been translated to
524 // CSS properties by ldomDocumentWriterFilter::OnAttribute():
525 // width= has been translated to elem style->width
526 // align= has been translated to elem style->text_align
527 // valign= has been translated to elem style->vertical_align
528 // (This allows overriding them with Style tweaks to remove
529 // publisher alignments and specified widths)
530 // Note: this is only done with plain .html files, but
531 // not with EPUBs.
532 // todo: see if needed with EPUBs
533
534 css_length_t w = style->width;
535 if ( w.type == css_val_percent ) { // %
536 cell->percent = w.value / 256;
537 }
538 else if ( w.type != css_val_unspecified ) { // px, em...
539 int em = item->getFont()->getSize();
540 cell->width = lengthToPx( w, 0, em );
541 }
542 // else: cell->percent and cell->width stay at 0
543
544 // This is not used here, but style->text_align will
545 // be naturally handled when cells are rendered
546 css_text_align_t ta = style->text_align;
547 if ( ta == css_ta_center )
548 cell->halign = 1; // center
549 else if ( ta == css_ta_right )
550 cell->halign = 2; // right
551
552 // https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
553 // The default for vertical_align is baseline (cell->valign=0,
554 // set at CCRTableCell init time), and all other named values
555 // than top/middle/bottom act as baseline.
556 css_length_t va = style->vertical_align;
557 if ( va.type == css_val_unspecified ) {
558 if ( va.value == css_va_top )
559 cell->valign = 1; // top
560 else if ( va.value == css_va_middle )
561 cell->valign = 2; // middle
562 else if ( va.value == css_va_bottom )
563 cell->valign = 3; // bottom
564 }
565
566 cell->direction = item_direction;
567 cell->row = rows[rows.length()-1];
568 cell->row->cells.add( cell );
569 cell->row->numcols += cell->colspan;
570 ExtendCols( cell->row->numcols ); // update col count
571 }
572 break;
573 case erm_inline:
574 // do nothing
575 break;
576 }
577 }
578 }
579 return 0;
580 }
581
FixRowGroupsOrder()582 void FixRowGroupsOrder() {
583 if ( !enhanced_rendering )
584 return;
585 if ( rowgroups.length() == 0 )
586 return;
587
588 // THEAD TBODY/TR TFOOT usually comes in this logical order,
589 // but with CSS, "display:table-header-group" might be used
590 // to render some element above others even if it is after
591 // them in the DOM.
592 // (This is done by the MathML CSS profile, for example with
593 // <msup>, to render the superscript in a top table row).
594 //
595 // Note that Firefox only moves the *first* table-header-group and
596 // the *first* table-footer-group met.
597 // https://www.w3.org/TR/CSS2/tables.html#table-display says the same:
598 // "If a table contains multiple elements with 'display: table-header-group',
599 // only the first is rendered as a header; the others are treated as if they
600 // had 'display: table-row-group'. "
601 // So, we will handle only the first ones met.
602 //
603 // (At this point, row->index have not yet been set, so
604 // we have just 'rowgroups' and 'rows' arrays, that we
605 // can just re-order without any other fix.)
606 //
607 // This might cause some issues if the reordered things contain
608 // some rowspan/colspan crossing row groups... Firefox limits
609 // the rowspan effect to inside each table group, but we don't
610 // do that. Hopefully, this kind of HTML error must be rare.
611
612 // Look for the first erm_table_header_group
613 for ( int i=0; i < rowgroups.length(); i++ ) {
614 if ( rowgroups[i]->kind == erm_table_header_group ) {
615 CCRTableRowGroup * first_header_group = rowgroups[i];
616 if ( i > 0 ) {
617 // It is not first in rowgroups: move it at start
618 rowgroups.move(0, i); // move(indexTo, indexFrom)
619 rows_rendering_reordered = true;
620 }
621 // Even if this group was first among groups, we may have
622 // before its rows other table-rows not part of any group:
623 // we need to move the rows part of this rowgroup before
624 // all other rows.
625 bool group_met = false;
626 int dest_idx = 0;
627 for ( int j=0; j < rows.length(); j++ ) {
628 if ( rows[j]->rowgroup == first_header_group ) {
629 if ( j != dest_idx ) {
630 rows.move(dest_idx, j); // move(indexTo, indexFrom)
631 rows_rendering_reordered = true;
632 dest_idx++;
633 }
634 group_met = true;
635 }
636 else if ( group_met ) {
637 // Not a row part of first_header_group: we moved
638 // all its rows, we're done.
639 break;
640 }
641 }
642 break; // Only deal with the first one met
643 }
644 }
645 // Look for the first erm_table_footer_group
646 for ( int i=0; i < rowgroups.length(); i++ ) {
647 if ( rowgroups[i]->kind == erm_table_footer_group ) {
648 CCRTableRowGroup * first_footer_group = rowgroups[i];
649 if ( i < rowgroups.length()-1 ) {
650 // It is not last in rowgroups: move it at end
651 rowgroups.move(rowgroups.length()-1, i); // move(indexTo, indexFrom)
652 rows_rendering_reordered = true;
653 }
654 // Even if this group was last among groups, we may have
655 // after its rows other table-rows not part of any group:
656 // we need to move the rows part of this rowgroup after
657 // all other rows.
658 bool group_met = false;
659 int dest_idx = rows.length()-1;
660 for ( int j=rows.length()-1; j >= 0; j-- ) {
661 if ( rows[j]->rowgroup == first_footer_group ) {
662 if ( j != dest_idx ) {
663 rows.move(dest_idx, j); // move(indexTo, indexFrom)
664 rows_rendering_reordered = true;
665 }
666 dest_idx--;
667 group_met = true;
668 }
669 else if ( group_met ) {
670 // Not a row part of first_footer_group: we moved
671 // all its rows, we're done.
672 break;
673 }
674 }
675 break; // Only deal with the first one met
676 }
677 }
678 }
679
680 // More or less complex algorithms to calculate column widths are described at:
681 // https://www.w3.org/TR/css-tables-3/#computing-cell-measures
682 // https://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2
683 // https://www.w3.org/TR/CSS2/tables.html#auto-table-layout
684 // https://drafts.csswg.org/css3-tables-algorithms/Overview.src.htm
685 // https://developer.mozilla.org/en-US/docs/Archive/Mozilla/Table_Layout_Strategy
686 // http://www.tads.org/t3doc/doc/htmltads/tables.htm
687 // https://github.com/Kozea/WeasyPrint (html to pdf in python)
688
689 // Beware of risk of division by zero!
690 // (In vim, find divisions with: /\v(\/)@<!(\*)@<!\/(\/)@!(\*)@!.* )
691
PlaceCells()692 void PlaceCells() {
693 int i, j;
694 // search for max column number
695 int maxcols = 0;
696 for (i=0; i<rows.length(); i++) {
697 if (maxcols<rows[i]->numcols)
698 maxcols=rows[i]->numcols;
699 }
700 // add column objects
701 ExtendCols(maxcols);
702 // place row cells horizontally
703 for (i=0; i<rows.length(); i++) {
704 int x=0;
705 int miny=-1;
706 CCRTableRow * row = rows[i];
707 row->index = i;
708 for (j=0; j<rows[i]->cells.length(); j++) {
709 CCRTableCell * cell = rows[i]->cells[j];
710 int cs = cell->colspan;
711 // Find col (in cols) that does not have something rowspan'ing
712 // current row and can accept this cell. Extend nb of cols until
713 // we find one
714 while (x<cols.length() && cols[x]->nrows>i) { // find free cell position
715 x++;
716 ExtendCols(x); // update col count
717 }
718 ExtendCols( x + cs ); // update col count
719 cell->col = cols[x];
720 // Update nrows (used rows last index) for columns this cell will
721 // colspan with the number of rows it will rowspan
722 for (int xx=0; xx<cs; xx++) {
723 // place cell
724 ExtendCols(x+xx+1); // update col count
725 if ( cols[x+xx]->nrows < i+cell->rowspan )
726 cols[x+xx]->nrows = i+cell->rowspan;
727 }
728 // update col width (for regular cells with colspan=1 only)
729 if (cell->colspan==1) {
730 if (cell->width>0 && cell->col->width<cell->width && cell->col->percent==0) {
731 cell->col->width = cell->width;
732 } else if (cell->percent>0 && cell->col->width==0 && cell->col->percent<cell->percent) {
733 cell->col->percent = cell->percent;
734 }
735 }
736 x += cs;
737 // Note: we don't handle rowspan/colspan conflicts like with:
738 // <table>
739 // <tr><td rowspan=3>col1</td> <td>col2 </td> <td rowspan=3>col3</td></tr>
740 // <tr><td colspan=2>col2</td></tr>
741 // <tr><td>col3b</td></tr>
742 // </table>
743 // Firefox seems to kill the colspan=2 and make it =1
744 }
745 /* The following code (now commented out) looks wrong:
746 * it's supposed to look at each col passed by our last
747 * cell (but not the cols on its right), and find out
748 * the one with the min number of rows occupied.
749 * If that min nb of rows is larger than our current
750 * row number, it would insert empty rows to fill it.
751 * By doing so, it was inserting rows in between
752 * existing ones, and messing with rowspans.
753 * For example, with:
754 * <table>
755 * <tr><td rowspan=3>col1</td> <td rowspan=3>col2</td></tr>
756 * <tr><td>col3a</td></tr>
757 * <tr><td>col3b</td></tr>
758 * </table>
759 * col3a abd col3b were pushed below col1+col2, instead
760 * of creating and being in a 3rd column on their right.
761 *
762 * I can't guess which other case this was supposed to solve...
763 * So let's disable it until we find why it was needed.
764 *
765 // update min row count
766 for (j=0; j<x; j++) {
767 if (miny==-1 || miny>cols[j]->nrows)
768 miny=cols[j]->nrows;
769 }
770 // skip fully filled rows!
771 while (miny>i+1) {
772 i++;
773 // add new row (already filled)
774 CCRTableRow * nrow = new CCRTableRow;
775 nrow->index = i;
776 rows.insert(i, nrow);
777 }
778 */
779 }
780 int maxy = 0; // check highest column
781 for (j=0; j<cols.length(); j++)
782 if (maxy<cols[j]->nrows)
783 maxy=cols[j]->nrows;
784 // padding table with empty lines up to max col height
785 while (maxy>i) {
786 i++;
787 // add new row (already filled)
788 CCRTableRow * nrow = new CCRTableRow;
789 nrow->index = i;
790 rows.insert(i, nrow);
791 }
792 // The above code has possibly added with ExtendCols() virtual columns
793 // (that do not contain any cell) to 'cols' to account for colspan
794 // values (which may be bogus in some documents).
795 // Remove any extraneous cols and rows, and adjust the colspan and
796 // rowspan values of involved cells.
797 int max_used_x = -1;
798 int max_used_y = -1;
799 for (i=0; i<rows.length(); i++) {
800 for (j=0; j<rows[i]->cells.length(); j++) {
801 CCRTableCell * cell = (rows[i]->cells[j]);
802 int x0 = cell->col->index;
803 if (x0 > max_used_x)
804 max_used_x = x0;
805 int y0 = cell->row->index;
806 if (y0 > max_used_y)
807 max_used_y = y0;
808 }
809 }
810 for (i=0; i<rows.length(); i++) {
811 for (j=0; j<rows[i]->cells.length(); j++) {
812 CCRTableCell * cell = (rows[i]->cells[j]);
813 if (cell->col->index + cell->colspan - 1 > max_used_x)
814 cell->colspan = max_used_x - cell->col->index + 1;
815 if (cell->row->index + cell->rowspan - 1 > max_used_y)
816 cell->rowspan = max_used_y - cell->row->index + 1;
817 if (is_rtl) // set up col_index, used for RTL re-ordering of cells
818 cell->col_index = cell->col->index;
819 }
820 }
821 #ifdef DEBUG_TABLE_RENDERING
822 printf("TABLE: grid %dx%d reduced to %dx%d\n",
823 cols.length(), rows.length(), max_used_x+1, max_used_y+1);
824 #endif
825 for (i=rows.length()-1; i>max_used_y; i--) {
826 if (rows[i]->rowgroup) {
827 rows[i]->rowgroup->rows.remove(rows[i]);
828 }
829 delete rows.remove(i);
830 }
831 for (i=cols.length()-1; i>max_used_x; i--) {
832 delete cols.remove(i);
833 // No need to adjust cols[x]->nrows, we don't use it from now
834 }
835
836 // If RTL table, we need to draw cells from right to left.
837 // So, just swap their order and update pointers at every place relevant
838 if ( is_rtl ) {
839 // Reverse cols (CCRTableCol):
840 cols.reverse();
841 int nbcols = cols.length();
842 for (i=0; i<nbcols; i++)
843 cols[i]->index = i;
844 // Reverse cells in each row
845 for (i=0; i<rows.length(); i++) {
846 rows[i]->cells.reverse();
847 // and update each cell pointer to the right CCRTableCol
848 for (j=0; j<rows[i]->cells.length(); j++) {
849 CCRTableCell * cell = rows[i]->cells[j];
850 cell->col = cols[nbcols-1 - cell->col_index - (cell->colspan-1)];
851 // cell->col_index has been set up above, and reflects
852 // the original index before we reversed cols.
853 }
854 }
855 }
856
857 css_style_ref_t table_style = elem->getStyle();
858 int table_em = elem->getFont()->getSize();
859 // border-spacing does not accept values in % unit
860 int borderspacing_h = lengthToPx(table_style->border_spacing[0], 0, table_em);
861 bool border_collapse = (table_style->border_collapse == css_border_collapse);
862
863 if (border_collapse) {
864 borderspacing_h = 0; // no border spacing when table collapse
865 // Each cell is responsible for drawing its borders.
866 for (i=0; i<rows.length(); i++) {
867 for (j=0; j<rows[i]->cells.length(); j++) {
868 CCRTableCell * cell = (rows[i]->cells[j]);
869 css_style_ref_t style = cell->elem->getStyle();
870 // (Note: we should not modify styles directly, as the change
871 // in style cache will affect other nodes with the same style,
872 // and corrupt style cache Hash, invalidating cache reuse.)
873 css_style_ref_t newstyle(new css_style_rec_t);
874 copystyle(style, newstyle);
875
876 // We don't do adjacent-cells "border size comparisons and
877 // take the larger one", as it's not obvious to get here
878 // this cell's adjactent ones (possibly multiple on a side
879 // when rowspan/colspan).
880 // But we should at least, for cells with no border, get the
881 // top and bottom, and the left and right for cells at
882 // table edges, from the TR, THEAD/TBODY or TABLE.
883 bool is_at_top = cell->row->index == 0;
884 bool is_at_bottom = (cell->row->index + cell->rowspan) == rows.length();
885 bool is_at_left = cell->col->index == 0;
886 bool is_at_right = (cell->col->index + cell->colspan) == cols.length();
887 // We'll avoid calling measureBorder() many times for this same cell,
888 // by passing these by reference to collapse_border():
889 int cell_border_top = measureBorder(cell->elem, 0);
890 int cell_border_right = measureBorder(cell->elem, 1);
891 int cell_border_bottom = measureBorder(cell->elem, 2);
892 int cell_border_left = measureBorder(cell->elem, 3);
893 //
894 // With border-collapse, a cell may get its top and bottom
895 // borders from its TR.
896 ldomNode * rtop = rows[cell->row->index]->elem;
897 // (may be NULL, but collapse_border() checks for that)
898 // For a cell with rowspan>1, not sure if its bottom border should come
899 // from its own starting row, or the other row it happens to end on.
900 // Should look cleaner if we use the later.
901 ldomNode * rbottom = rows[cell->row->index + cell->rowspan - 1]->elem;
902 collapse_border(newstyle, cell_border_top, 0, rtop);
903 collapse_border(newstyle, cell_border_bottom, 2, rbottom);
904 // We also get the left and right borders, for the first or
905 // the last cell in a row, from the row (top row if multi rows span)
906 if (is_at_left)
907 collapse_border(newstyle, cell_border_left, 3, rtop);
908 if (is_at_right)
909 collapse_border(newstyle, cell_border_right, 1, rtop);
910 // If a row is missing some cells, there is none that stick
911 // to the right of the table (is_at_right is false): the outer
912 // table border will have a hole for this row...
913 // We may also get them from the rowgroup this TR is part of, if any, for
914 // the cells in the first or the last row of this rowgroup
915 if (rows[cell->row->index]->rowgroup) {
916 CCRTableRowGroup * grp = rows[cell->row->index]->rowgroup;
917 if (rows[cell->row->index] == grp->rows.first())
918 collapse_border(newstyle, cell_border_top, 0, grp->elem);
919 if (rows[cell->row->index] == grp->rows.last())
920 collapse_border(newstyle, cell_border_bottom, 2, grp->elem);
921 if (is_at_left)
922 collapse_border(newstyle, cell_border_left, 3, grp->elem);
923 if (is_at_right)
924 collapse_border(newstyle, cell_border_right, 1, grp->elem);
925 }
926 // And we may finally get borders from the table itself ("elem")
927 if (is_at_top)
928 collapse_border(newstyle, cell_border_top, 0, elem);
929 if (is_at_bottom)
930 collapse_border(newstyle, cell_border_bottom, 2, elem);
931 if (is_at_left)
932 collapse_border(newstyle, cell_border_left, 3, elem);
933 if (is_at_right)
934 collapse_border(newstyle, cell_border_right, 1, elem);
935
936 // Now, we should disable some borders for this cell,
937 // at inter-cell boundaries.
938 // We could either keep the right and bottom borders
939 // (which would be better to catch coordinates bugs).
940 // Or keep the top and left borders, which is better:
941 // if a cell carries its top border, when splitting
942 // rows to pages, a row at top of page will have its
943 // top border (unlike previous alternative), which is
944 // clearer for the reader.
945 // So, we disable the bottom and right borders, except
946 // for cells that are on the right or at the bottom of
947 // the table, as these will draw the outer table border
948 // on these sides.
949 if ( !is_at_right )
950 newstyle->border_style_right = css_border_none;
951 if ( !is_at_bottom )
952 newstyle->border_style_bottom = css_border_none;
953
954 cell->elem->setStyle(newstyle);
955 // (Note: we should no more modify a style after it has been
956 // applied to a node with setStyle().)
957 }
958 // (Some optimisation could be made in these loops, as
959 // collapse_border() currently calls measureBorder() many
960 // times for the same elements: TR, TBODY, TABLE...)
961 }
962 // The TR and TFOOT borders will explicitely NOT be drawn by DrawDocument()
963 // (Firefox never draws them, even when no border-collapse).
964 // But the TABLE ones will be (needed when no border-collapse).
965 // So, as we possibly collapsed the table border to the cells, we just
966 // set them to none on the TABLE.
967 css_style_ref_t style = elem->getStyle();
968 css_style_ref_t newstyle(new css_style_rec_t);
969 copystyle(style, newstyle);
970 newstyle->border_style_top = css_border_none;
971 newstyle->border_style_right = css_border_none;
972 newstyle->border_style_bottom = css_border_none;
973 newstyle->border_style_left = css_border_none;
974 elem->setStyle(newstyle);
975 }
976 // We should no longer modify cells' padding and border beyond this point,
977 // as these are used below to compute max_content_width and min_content_width,
978 // which account for them.
979
980 // Compute for each cell the max (prefered if possible) and min (width of the
981 // longest word, so no word is cut) rendered widths.
982 for (i=0; i<rows.length(); i++) {
983 for (j=0; j<rows[i]->cells.length(); j++) {
984 // rows[i]->cells contains only real cells made from node elements
985 CCRTableCell * cell = (rows[i]->cells[j]);
986 getRenderedWidths(cell->elem, cell->max_content_width, cell->min_content_width, cell->direction);
987 #ifdef DEBUG_TABLE_RENDERING
988 printf("TABLE: cell[%d,%d] getRenderedWidths: %d (min %d)\n",
989 j, i, cell->max_content_width, cell->min_content_width);
990 #endif
991 int x0 = cell->col->index;
992 if (cell->colspan == 1) {
993 cols[x0]->sum_max_content_width += cell->max_content_width;
994 cols[x0]->nb_sum_max_content_width += 1;
995 // Update cols max/min widths only for colspan=1 cells
996 if ( cell->max_content_width > cols[x0]->max_width )
997 cols[x0]->max_width = cell->max_content_width;
998 if ( cell->min_content_width > cols[x0]->min_width )
999 cols[x0]->min_width = cell->min_content_width;
1000 }
1001 }
1002 }
1003 // Second pass for cells with colspan > 1
1004 for (i=0; i<rows.length(); i++) {
1005 for (j=0; j<rows[i]->cells.length(); j++) {
1006 CCRTableCell * cell = (rows[i]->cells[j]);
1007 if (cell->colspan > 1) {
1008 // Check if we need to update the max_width and min_width
1009 // of each cols we span
1010 int nbspans = cell->colspan;
1011 int x0 = cell->col->index;
1012 // Get the existing aggregated min/max width of the cols we span
1013 int cols_max_width = 0;
1014 int cols_min_width = 0;
1015 for (int x=0; x<nbspans; x++) {
1016 cols_min_width += cols[x0+x]->min_width;
1017 cols_max_width += cols[x0+x]->max_width;
1018 }
1019 cols_min_width += borderspacing_h * (nbspans-1);
1020 cols_max_width += borderspacing_h * (nbspans-1);
1021 #ifdef DEBUG_TABLE_RENDERING
1022 printf("TABLE: COLS SPANNED[%d>%d] min_width=%d max_width=%d\n",
1023 x0, x0+nbspans-1, cols_min_width, cols_max_width);
1024 #endif
1025 if ( cell->min_content_width > cols_min_width ) {
1026 // Our min width is larger than the spanned cols min width
1027 int to_distribute = cell->min_content_width - cols_min_width;
1028 int distributed = 0;
1029 for (int x=0; x<nbspans; x++) {
1030 // Distribute more to bigger min_width to keep original
1031 // cell proportions
1032 int this_dist;
1033 if (cols_min_width > 0)
1034 this_dist = to_distribute * cols[x0+x]->min_width / cols_min_width;
1035 else
1036 this_dist = to_distribute / nbspans;
1037 cols[x0+x]->min_width += this_dist;
1038 distributed += this_dist;
1039 #ifdef DEBUG_TABLE_RENDERING
1040 printf("TABLE: COL[%d] (todist:%d) min_wdith += %d => %d\n",
1041 x0+x, to_distribute, this_dist, cols[x0+x]->min_width);
1042 #endif
1043 }
1044 // Distribute left over to last col
1045 cols[x0+nbspans-1]->min_width += to_distribute - distributed;
1046 #ifdef DEBUG_TABLE_RENDERING
1047 printf("TABLE: COL[%d] (leftover:%d-%d) min_wdith += %d => %d\n",
1048 x0+nbspans-1, to_distribute, distributed, to_distribute - distributed,
1049 cols[x0+nbspans-1]->min_width);
1050 #endif
1051 }
1052 // Let's do the same for max_width, although it may lead to
1053 // messier layouts in complex colspan setups...
1054 // (And we should probably not let all cols->max_width at 0 if they are 0!)
1055 if ( cell->max_content_width > cols_max_width ) {
1056 // Our max width is larger than the spanned cols max width
1057 int to_distribute = cell->max_content_width - cols_max_width;
1058 int distributed = 0;
1059 for (int x=0; x<nbspans; x++) {
1060 // Distribute more to bigger max_width to keep original
1061 // cell proportions
1062 int this_dist;
1063 if (cols_max_width > 0)
1064 this_dist = to_distribute * cols[x0+x]->max_width / cols_max_width;
1065 else
1066 this_dist = to_distribute / nbspans;
1067 cols[x0+x]->max_width += this_dist;
1068 distributed += this_dist;
1069 #ifdef DEBUG_TABLE_RENDERING
1070 printf("TABLE: COL[%d] (todist:%d) max_wdith += %d => %d\n",
1071 x0+x, to_distribute, this_dist, cols[x0+x]->max_width);
1072 #endif
1073 }
1074 // Distribute left over to last col
1075 cols[x0+nbspans-1]->max_width += to_distribute - distributed;
1076 #ifdef DEBUG_TABLE_RENDERING
1077 printf("TABLE: COL[%d] (leftover:%d-%d) max_wdith += %d => %d\n",
1078 x0+nbspans-1, to_distribute, distributed, to_distribute - distributed,
1079 cols[x0+nbspans-1]->max_width);
1080 #endif
1081 }
1082 // Also distribute to sum_max_content_width
1083 for (int x=0; x<nbspans; x++) {
1084 int this_dist;
1085 if (cols_max_width > 0)
1086 this_dist = cell->max_content_width * cols[x0+x]->max_width / cols_max_width;
1087 else
1088 this_dist = cell->max_content_width / nbspans;
1089 cols[x0+x]->sum_max_content_width += this_dist;
1090 cols[x0+x]->nb_sum_max_content_width += 1;
1091 }
1092 }
1093 }
1094 }
1095
1096 /////////////////////////// From here until further noticed, we just use and update the cols objects
1097 // Find width available for cells content (including their borders and paddings)
1098 // Start with table full width
1099 int assignable_width = table_width;
1100 // Remove table outer borders
1101 assignable_width -= measureBorder(elem,1) + measureBorder(elem,3); // (border indexes are TRBL)
1102 if ( border_collapse ) {
1103 // Table own outer paddings and any border-spacing are
1104 // ignored with border-collapse
1105 }
1106 else { // no collapse
1107 // Remove table outer paddings (margin and padding indexes are LRTB)
1108 assignable_width -= lengthToPx(table_style->padding[0], table_width, table_em);
1109 assignable_width -= lengthToPx(table_style->padding[1], table_width, table_em);
1110 // Remove (nb cols + 1) border-spacing
1111 assignable_width -= (cols.length() + 1) * borderspacing_h;
1112 }
1113 #ifdef DEBUG_TABLE_RENDERING
1114 printf("TABLE: table_width=%d assignable_width=%d\n", table_width, assignable_width);
1115 #endif
1116 if (assignable_width <= 0) { // safety check
1117 // In case we get a zero or negative value (too much padding or
1118 // borderspacing and many columns), just re-set it to table_width
1119 // for our calculation purpose, which expect a positive value to
1120 // work with: the rendering will be bad, but some stuff will show.
1121 assignable_width = table_width;
1122 borderspacing_h = 0;
1123 }
1124
1125 // Find best width for each column
1126 int npercent=0;
1127 int sumpercent=0;
1128 int nwidth = 0;
1129 int sumwidth = 0;
1130 for (int x=0; x<cols.length(); x++) {
1131 #ifdef DEBUG_TABLE_RENDERING
1132 printf("TABLE WIDTHS step1: cols[%d]: %d%% %dpx\n",
1133 x, cols[x]->percent, cols[x]->width);
1134 #endif
1135 if (cols[x]->percent>0) {
1136 cols[x]->width_auto = false;
1137 sumpercent += cols[x]->percent;
1138 cols[x]->width = 0;
1139 npercent++;
1140 } else if (cols[x]->width>0) {
1141 cols[x]->width_auto = false;
1142 sumwidth += cols[x]->width;
1143 nwidth++;
1144 }
1145 }
1146 int nrest = cols.length() - nwidth - npercent; // nb of cols with auto width
1147 int sumwidthpercent = 0; // percent of sum-width
1148 // Percents are to be used as a ratio of assignable_width (if we would
1149 // use the original full table width, 100% would overflow with borders,
1150 // paddings and border spacings)
1151 if (sumwidth) {
1152 sumwidthpercent = 100*sumwidth/assignable_width;
1153 if (sumpercent+sumwidthpercent+5*nrest>100) { // 5% (?) for each unsized column
1154 // too wide: convert widths to percents
1155 for (int i=0; i<cols.length(); i++) {
1156 if (cols[i]->width>0) {
1157 cols[i]->percent = cols[i]->width*100/assignable_width;
1158 cols[i]->width = 0;
1159 sumpercent += cols[i]->percent;
1160 npercent++;
1161 }
1162 }
1163 }
1164 }
1165 // scale percents
1166 int maxpercent = 100-3*nrest; // 3% (?) for each unsized column
1167 if (sumpercent>maxpercent && sumpercent>0) {
1168 // scale percents
1169 // int newsumpercent = 0;
1170 for (int i=0; i<cols.length(); i++) {
1171 if (cols[i]->percent>0) {
1172 cols[i]->percent = cols[i]->percent*maxpercent/sumpercent;
1173 // newsumpercent += cols[i]->percent;
1174 cols[i]->width = 0;
1175 }
1176 }
1177 // sumpercent = newsumpercent;
1178 }
1179 // calc width by percents
1180 sumwidth = 0;
1181 int sum_auto_max_width = 0;
1182 int sum_auto_mean_max_content_width = 0;
1183 nwidth = 0;
1184 for (i=0; i<cols.length(); i++) {
1185 if (cols[i]->percent>0) {
1186 cols[i]->width = assignable_width * cols[i]->percent / 100;
1187 cols[i]->percent = 0;
1188 }
1189 if (cols[i]->width>0) {
1190 // calc width stats
1191 sumwidth += cols[i]->width;
1192 nwidth++;
1193 } else if (cols[i]->max_width>0) {
1194 sum_auto_max_width += cols[i]->max_width;
1195 sum_auto_mean_max_content_width += cols[i]->sum_max_content_width / cols[i]->nb_sum_max_content_width;
1196 }
1197 }
1198 #ifdef DEBUG_TABLE_RENDERING
1199 for (int x=0; x<cols.length(); x++)
1200 printf("TABLE WIDTHS step2: cols[%d]: %d%% %dpx\n",
1201 x, cols[x]->percent, cols[x]->width);
1202 #endif
1203 // At this point, all columns with specified width or percent has been
1204 // set accordingly, or reduced to fit table width
1205 // We need to compute a width for columns with unspecified width.
1206 // nrest = cols.length() - nwidth;
1207 int restwidth = assignable_width - sumwidth;
1208 int sumMinWidths = 0;
1209 // new pass: convert text len percent into width
1210 for (i=0; i<cols.length(); i++) {
1211 if (cols[i]->width==0) { // unspecified (or width scaled down and rounded to 0)
1212 // We have multiple options:
1213 // Either distribute remaining width according to max_width ratio
1214 // (so larger content gets more width to use less height)
1215 // if (sum_auto_max_width > 0)
1216 // cols[i]->width = cols[i]->max_width * restwidth / sum_auto_max_width;
1217 // Or better: distribute remaining width according to the mean
1218 // of cells' max_content_width, so a single large cell among
1219 // many smaller cells doesn't request all the width for this
1220 // column (which would give less to others, which could make
1221 // them use more height). This should help getting more
1222 // reasonable heights across all rows.
1223 if (sum_auto_mean_max_content_width > 0)
1224 cols[i]->width = cols[i]->sum_max_content_width / cols[i]->nb_sum_max_content_width * restwidth / sum_auto_mean_max_content_width;
1225 // else stays at 0
1226 sumwidth += cols[i]->width;
1227 nwidth++;
1228 }
1229 if (cols[i]->width < cols[i]->min_width) {
1230 // extend too small cols to their min_width
1231 int delta = cols[i]->min_width - cols[i]->width;
1232 cols[i]->width += delta;
1233 sumwidth += delta;
1234 }
1235 sumMinWidths += cols[i]->min_width; // will be handy later
1236 }
1237 if (sumwidth>assignable_width && sumwidth>0) {
1238 // too wide! rescale down
1239 int newsumwidth = 0;
1240 for (i=0; i<cols.length(); i++) {
1241 cols[i]->width = cols[i]->width * assignable_width / sumwidth;
1242 newsumwidth += cols[i]->width;
1243 }
1244 sumwidth = newsumwidth;
1245 }
1246 #ifdef DEBUG_TABLE_RENDERING
1247 for (int x=0; x<cols.length(); x++)
1248 printf("TABLE WIDTHS step3: cols[%d]: %d%% %dpx (min:%d / max:%d)\n",
1249 x, cols[x]->percent, cols[x]->width, cols[x]->min_width, cols[x]->max_width);
1250 #endif
1251 bool canFitMinWidths = (sumMinWidths > 0 && sumMinWidths <= assignable_width);
1252 // new pass: resize columns with originally unspecified widths
1253 int rw=0;
1254 int dist_nb_cols = 0;
1255 for (int x=0; x<cols.length(); x++) {
1256 // Adjust it further down to the measured max_width
1257 int prefered_width = cols[x]->min_width;
1258 if (cols[x]->max_width > prefered_width) {
1259 prefered_width = cols[x]->max_width;
1260 }
1261 if (cols[x]->width_auto && cols[x]->width > prefered_width) {
1262 // Column can nicely fit in a smaller width
1263 rw += (cols[x]->width - prefered_width);
1264 cols[x]->width = prefered_width;
1265 // min_width is no longer needed: use it as a flag so we don't
1266 // redistribute width to this column
1267 cols[x]->min_width = -1;
1268 }
1269 else if (canFitMinWidths && cols[x]->width < cols[x]->min_width) {
1270 // Even for columns with originally specified width:
1271 // If all min_width can fit into available width, ensure
1272 // cell's min_width by taking back from to-be redistributed width
1273 rw -= (cols[x]->min_width - cols[x]->width);
1274 cols[x]->width = cols[x]->min_width;
1275 // min_width is no longer needed: use it as a flag so we don't
1276 // redistribute width to this column
1277 cols[x]->min_width = -1;
1278 }
1279 else if (cols[x]->width_auto && cols[x]->min_width > 0) {
1280 dist_nb_cols += 1; // candidate to get more width
1281 }
1282 }
1283 #ifdef DEBUG_TABLE_RENDERING
1284 for (int x=0; x<cols.length(); x++)
1285 printf("TABLE WIDTHS step4: cols[%d]: %d%% %dpx (min %dpx)\n",
1286 x, cols[x]->percent, cols[x]->width, cols[x]->min_width);
1287 #endif
1288 int restw = assignable_width - sumwidth + rw; // may be negative if we needed to
1289 // increase to fulfill min_width
1290 if (shrink_to_fit && restw > 0) {
1291 // If we're asked to shrink width to fit cells content, don't
1292 // distribute restw to columns, but shrink table width
1293 // Table padding may be in %, and need to be corrected
1294 int correction = 0;
1295 correction += lengthToPx(table_style->padding[0], table_width, table_em);
1296 correction += lengthToPx(table_style->padding[0], table_width, table_em);
1297 table_width -= restw;
1298 correction -= lengthToPx(table_style->padding[0], table_width, table_em);
1299 correction -= lengthToPx(table_style->padding[0], table_width, table_em);
1300 table_width -= correction;
1301 #ifdef DEBUG_TABLE_RENDERING
1302 assignable_width -= restw + correction; // (for debug printf() below)
1303 printf("TABLE WIDTHS step5 (fit): reducing table_width %d -%d -%d > %d\n",
1304 table_width+restw+correction, restw, correction, table_width);
1305 #endif
1306 }
1307 else {
1308 #ifdef DEBUG_TABLE_RENDERING
1309 printf("TABLE WIDTHS step5 (dist): %d to distribute to %d cols\n",
1310 restw, dist_nb_cols);
1311 #endif
1312 // distribute rest of width between all cols that can benefit from more
1313 bool dist_all_non_empty_cols = false;
1314 if (dist_nb_cols == 0) {
1315 dist_all_non_empty_cols = true;
1316 // distribute to all non empty cols
1317 for (int x=0; x<cols.length(); x++) {
1318 if (cols[x]->min_width != 0) {
1319 dist_nb_cols += 1;
1320 }
1321 }
1322 #ifdef DEBUG_TABLE_RENDERING
1323 printf("TABLE WIDTHS step5: %d to distribute to all %d non empty cols\n",
1324 restw, dist_nb_cols);
1325 #endif
1326 }
1327 if (restw != 0 && dist_nb_cols>0) {
1328 int a = restw / dist_nb_cols;
1329 int b = restw % dist_nb_cols;
1330 for (i=0; i<cols.length(); i++) {
1331 if ( (!dist_all_non_empty_cols && cols[i]->min_width > 0)
1332 || (dist_all_non_empty_cols && cols[i]->min_width != 0) ) {
1333 cols[i]->width += a;
1334 if (b>0) {
1335 cols[i]->width ++;
1336 b--;
1337 }
1338 else if (b<0) {
1339 cols[i]->width --;
1340 b++;
1341 }
1342 }
1343 }
1344 // (it would be better to distribute restw according
1345 // to each column max_width / min_width, so larger ones
1346 // get more of it)
1347 }
1348 #ifdef DEBUG_TABLE_RENDERING
1349 for (int x=0; x<cols.length(); x++)
1350 printf("TABLE WIDTHS step5: cols[%d]: %d%% %dpx\n",
1351 x, cols[x]->percent, cols[x]->width);
1352 #endif
1353 }
1354 #ifdef DEBUG_TABLE_RENDERING
1355 printf("TABLE WIDTHS SUM:");
1356 int colswidthsum = 0;
1357 for (int x=0; x<cols.length(); x++) {
1358 printf(" +%d", cols[x]->width);
1359 colswidthsum += cols[x]->width;
1360 }
1361 printf(" = %d", colswidthsum);
1362 if (assignable_width == colswidthsum)
1363 printf(" == assignable_width, GOOD\n");
1364 else
1365 printf(" != assignable_width %d, BAAAAAAADDD\n", assignable_width);
1366 #endif
1367
1368 // update col x
1369 for (i=0; i<cols.length(); i++) {
1370 if (i == 0)
1371 cols[i]->x = borderspacing_h;
1372 else
1373 cols[i]->x = cols[i-1]->x + cols[i-1]->width + borderspacing_h;
1374 #ifdef DEBUG_TABLE_RENDERING
1375 printf("TABLE WIDTHS step6: cols[%d]->x = %d\n", i, cols[i]->x);
1376 #endif
1377 }
1378 /////////////////////////// Done with just using and updating the cols objects
1379
1380 // Columns widths calculated ok!
1381 // Update width of each cell
1382 for (i=0; i<rows.length(); i++) {
1383 for (j=0; j<rows[i]->cells.length(); j++) {
1384 CCRTableCell * cell = (rows[i]->cells[j]);
1385 cell->width = 0;
1386 int x0 = cell->col->index;
1387 for (int x=0; x<cell->colspan; x++) {
1388 cell->width += cols[x0+x]->width;
1389 }
1390 if ( cell->colspan > 1 ) {
1391 // include skipped borderspacings in cell width
1392 cell->width += borderspacing_h * (cell->colspan-1);
1393 }
1394 #ifdef DEBUG_TABLE_RENDERING
1395 printf("TABLE: placeCell[%d,%d] width: %d\n", j, i, cell->width);
1396 #endif
1397 }
1398 }
1399 }
1400
renderCells(LVRendPageContext & context)1401 int renderCells( LVRendPageContext & context )
1402 {
1403 // We should set, for each of the table children and sub-children,
1404 // its RenderRectAccessor fmt(node) x/y/w/h.
1405 // x/y of a cell are relative to its own parent node top left corner
1406 int em = elem->getFont()->getSize();
1407 css_style_ref_t table_style = elem->getStyle();
1408 int table_border_top = measureBorder(elem, 0);
1409 int table_border_right = measureBorder(elem, 1);
1410 int table_border_bottom = measureBorder(elem, 2);
1411 int table_border_left = measureBorder(elem, 3);
1412 int table_padding_left = lengthToPx(table_style->padding[0], table_width, em);
1413 int table_padding_right = lengthToPx(table_style->padding[1], table_width, em);
1414 int table_padding_top = lengthToPx(table_style->padding[2], table_width, em);
1415 int table_padding_bottom = lengthToPx(table_style->padding[3], table_width, em);
1416 int borderspacing_v = lengthToPx(table_style->border_spacing[1], 0, em);
1417 bool border_collapse = (table_style->border_collapse==css_border_collapse);
1418 if (border_collapse) {
1419 table_padding_top = 0;
1420 table_padding_bottom = 0;
1421 table_padding_left = 0;
1422 table_padding_right = 0;
1423 borderspacing_v = 0;
1424 }
1425 // We want to distribute border spacing on top and bottom of each row,
1426 // mainly for page splitting to carry half of it on each page.
1427 int borderspacing_v_top = borderspacing_v / 2;
1428 int borderspacing_v_bottom = borderspacing_v - borderspacing_v_top;
1429 // (Both will be 0 if border_collapse)
1430
1431 int nb_rows = rows.length();
1432
1433 // We will context.AddLine() for page splitting the elements
1434 // (caption, rows) as soon as we meet them and their y-positionings
1435 // inside the tables are known and won't change.
1436 // (This would need that rowgroups be dealt with in this flow (and
1437 // not at the end) if we change the fact that we ignore their
1438 // border/padding/margin - see below why we do.)
1439 lvRect rect;
1440 elem->getAbsRect(rect);
1441 const int table_y0 = rect.top; // absolute y in document for top of table
1442 int last_y = table_y0; // used as y0 to AddLine(y0, table_y0+table_h)
1443 int line_flags = 0;
1444
1445 // Final table height will be added to as we meet table content
1446 int table_h = 0;
1447 table_h += table_border_top;
1448
1449 // render caption
1450 if ( caption ) {
1451 int em = caption->getFont()->getSize();
1452 int w = table_width - table_border_left - table_border_right;
1453 // (When border-collapse, these table_border_* will be 0)
1454 // Note: table padding does not apply to caption, and table padding-top
1455 // should be applied between the caption and the first row
1456 // Also, Firefox does not include the caption inside the table outer border.
1457 // We'll display as Firefox when border-collapse, as the table borders were
1458 // reset to 0 after we collapsed them to the cells.
1459 // But not when not border-collapse: we can't do that in crengine because
1460 // of parent>children boxes containment, so the caption will be at top
1461 // inside the table border.
1462 // A caption can have borders, that we must include in its padding:
1463 // we may then get a double border with the table one... (We could hack
1464 // caption->style to remove its border if the table has some, if needed.)
1465 LFormattedTextRef txform;
1466 RenderRectAccessor fmt( caption );
1467 fmt.setX( table_border_left );
1468 fmt.setY( table_h );
1469 fmt.setWidth( w ); // fmt.width must be set before 'caption->renderFinalBlock'
1470 // to have text-indent in % not mess up at render time
1471 css_style_ref_t caption_style = caption->getStyle();
1472 int padding_left = lengthToPx( caption_style->padding[0], w, em ) + measureBorder(caption, 3);
1473 int padding_right = lengthToPx( caption_style->padding[1], w, em ) + measureBorder(caption,1);
1474 int padding_top = lengthToPx( caption_style->padding[2], w, em ) + measureBorder(caption,0);
1475 int padding_bottom = lengthToPx( caption_style->padding[3], w, em ) + measureBorder(caption,2);
1476 if ( enhanced_rendering ) {
1477 // As done in renderBlockElementEnhanced when erm_final
1478 fmt.setInnerX( padding_left );
1479 fmt.setInnerY( padding_top );
1480 fmt.setInnerWidth( w - padding_left - padding_right );
1481 RENDER_RECT_SET_FLAG(fmt, INNER_FIELDS_SET);
1482 RENDER_RECT_SET_DIRECTION(fmt, caption_direction);
1483 fmt.setLangNodeIndex( TextLangMan::getLangNodeIndex(caption) );
1484 }
1485 fmt.push();
1486 caption_h = caption->renderFinalBlock( txform, &fmt, w - padding_left - padding_right );
1487 caption_h += padding_top + padding_bottom;
1488 // Reload fmt, as enode->renderFinalBlock() may have updated it.
1489 fmt = RenderRectAccessor( caption );
1490 fmt.setHeight( caption_h );
1491 fmt.push();
1492 table_h += caption_h;
1493 }
1494 table_h += table_padding_top; // padding top applies after caption
1495 if (nb_rows > 0) {
1496 // There must be the full borderspacing_v above first row.
1497 // Includes half of it here, and the other half when adding the row
1498 table_h += borderspacing_v_bottom;
1499 }
1500 if ( context.wantsLines() ) {
1501 // Includes table border top + full caption if any + table padding
1502 // top + half of borderspacing_v.
1503 // We ask for a split between these and the first row to be avoided,
1504 // but if it can't, padding-top will be on previous page, leaving
1505 // more room for the big first row on next page.
1506 // Any table->style->page-break-before AVOID or ALWAYS has been
1507 // taken care of by renderBlockElement(), so we can use AVOID here.
1508 if ( !enhanced_rendering )
1509 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1510 else
1511 // but not if called from RenderBlockElementEnhanced, where
1512 // margins handle page splitting a bit differently
1513 line_flags = RN_SPLIT_BEFORE_AUTO | RN_SPLIT_AFTER_AVOID;
1514 if (is_rtl)
1515 line_flags |= RN_LINE_IS_RTL;
1516 context.AddLine(last_y, table_y0 + table_h, line_flags);
1517 last_y = table_y0 + table_h;
1518 }
1519
1520 // If table is a single column, we can add to main context
1521 // the lines of each cell, instead of full table rows, which
1522 // will avoid cell lines to possibly be cut in the middle.
1523 // (When multi-columns, because same-row cells' lines may
1524 // not align, and because it's easier for the reader to
1525 // not have to go back one page to read next cell (from
1526 // same row) content, each row is considered a single
1527 // line in the matter of page splitting.)
1528 bool is_single_column = false;
1529 int min_row_height_for_split_by_line;
1530 if ( context.wantsLines() && cols.length() == 1 && enhanced_rendering ) {
1531 is_single_column = true;
1532 // We actually don't know if splitting by line is better
1533 // than splitting by row. By-row might be better if it's
1534 // a real table where rows really list items, while by-lines
1535 // is preferable where the table is just used as a content
1536 // wrapper. So, let's follow this simple rule from:
1537 // https://www.w3.org/TR/css-tables-3/#fragmentation
1538 // "must attempt to preserve the table rows unfragmented if
1539 // the cells spanning the row do not span any subsequent row,
1540 // and their height is at least twice smaller than both the
1541 // fragmentainer height and width. Other rows are said
1542 // freely fragmentable."
1543 // which looks like it applies to multi-columns tables too
1544 // (but our lvpagesplitter will manage this as fine) - but
1545 // let's just use that rule for single-columns tables.
1546 int page_height = elem->getDocument()->getPageHeight();
1547 int page_width = elem->getDocument()->getPageWidth();
1548 if ( page_width < page_height )
1549 min_row_height_for_split_by_line = page_width / 2;
1550 else
1551 min_row_height_for_split_by_line = page_height / 2;
1552 // Looks like cells having rowspan > 1 (which makes no sense
1553 // if only own column) shouldn't need any specific tweaks.
1554 // Also, as we don't apply cells and row style height, and
1555 // vertical-align shouldn't change anything if there are no
1556 // other cell that would increase the row height, this too
1557 // shouldn't need any check.
1558 // (Note that a TD style height will still be applied if rendered
1559 // as erm_block (but not if erm_final), but vertical-align won't
1560 // be ensured. todo: make that consistent.)
1561 // todo: we could also try to do that even if multi columns,
1562 // by merging multiple cells' LVRendPageContext.
1563 }
1564
1565 int i, j;
1566 // Calc individual cells dimensions
1567 for (i=0; i<rows.length(); i++) {
1568 CCRTableRow * row = rows[i];
1569 bool row_has_baseline_aligned_cells = false;
1570 for (j=0; j<rows[i]->cells.length(); j++) {
1571 CCRTableCell * cell = rows[i]->cells[j];
1572 // int x = cell->col->index;
1573 int y = cell->row->index;
1574 // int n = rows[i]->cells.length();
1575 if ( i==y ) { // upper left corner of cell
1576 // We need to render the cell to get its height
1577 if ( cell->elem->getRendMethod() == erm_final ) {
1578 LFormattedTextRef txform;
1579 int em = cell->elem->getFont()->getSize();
1580 css_style_ref_t elem_style = cell->elem->getStyle();
1581 int border_left = measureBorder(cell->elem,3);
1582 int border_right = measureBorder(cell->elem,1);
1583 int padding_left = lengthToPx( elem_style->padding[0], cell->width, em ) + border_left;
1584 int padding_right = lengthToPx( elem_style->padding[1], cell->width, em ) + border_right;
1585 int padding_top = lengthToPx( elem_style->padding[2], cell->width, em ) + measureBorder(cell->elem,0);
1586 int padding_bottom = lengthToPx( elem_style->padding[3], cell->width, em ) + measureBorder(cell->elem,2);
1587 RenderRectAccessor fmt( cell->elem );
1588 fmt.setWidth( cell->width ); // needed before calling elem->renderFinalBlock
1589 if ( is_ruby_table )
1590 RENDER_RECT_SET_FLAG(fmt, NO_INTERLINE_SCALE_UP);
1591 if ( enhanced_rendering ) {
1592 // As done in renderBlockElementEnhanced when erm_final
1593 fmt.setInnerX( padding_left );
1594 fmt.setInnerY( padding_top );
1595 fmt.setInnerWidth( cell->width - padding_left - padding_right );
1596 fmt.setUsableLeftOverflow( padding_left - border_left );
1597 fmt.setUsableRightOverflow( padding_right - border_right );
1598 RENDER_RECT_SET_FLAG(fmt, INNER_FIELDS_SET);
1599 RENDER_RECT_SET_DIRECTION(fmt, cell->direction);
1600 fmt.setLangNodeIndex( TextLangMan::getLangNodeIndex(cell->elem) );
1601 }
1602 fmt.push();
1603 int h = cell->elem->renderFinalBlock( txform, &fmt, cell->width - padding_left - padding_right);
1604 cell->height = padding_top + h + padding_bottom;
1605 // A cell baseline is the baseline of its first line of text (or
1606 // the bottom of content edge of the cell if no line)
1607 if ( txform->GetLineCount() > 0 ) // we have a line
1608 cell->baseline = padding_top + txform->GetLineInfo(0)->baseline;
1609 else // no line, no image: bottom of content edge is at padding_top
1610 cell->baseline = padding_top;
1611
1612 if ( cell->valign == 0 ) { // vertical-align: baseline
1613 // We'll use that baseline
1614 cell->adjusted_baseline = cell->baseline;
1615 }
1616 else { // all other vertical-align: values
1617 // "If a row has no cell box aligned to its baseline,
1618 // the baseline of that row is the bottom content edge
1619 // of the lowest cell in the row."
1620 // We'll position that bottom content edge
1621 cell->adjusted_baseline = padding_top + h;
1622 }
1623
1624 // Gather footnotes links, as done in renderBlockElement() when erm_final/flgSplit
1625 // and cell lines when is_single_column:
1626 if ( elem->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) || is_single_column ) {
1627 int orphans;
1628 int widows;
1629 if ( is_single_column ) {
1630 orphans = (int)(elem_style->orphans) - (int)(css_orphans_widows_1) + 1;
1631 widows = (int)(elem_style->widows) - (int)(css_orphans_widows_1) + 1;
1632 // We use a LVRendPageContext that gathers links by line,
1633 // so we can transfer them line by line to the upper/main context
1634 row->single_col_context = new LVRendPageContext(NULL, context.getPageHeight());
1635 row->single_col_context->AddLine(0, padding_top, RN_SPLIT_AFTER_AVOID);
1636 }
1637 int count = txform->GetLineCount();
1638 for (int i=0; i<count; i++) {
1639 const formatted_line_t * line = txform->GetLineInfo(i);
1640 int link_insert_pos; // used if is_single_column
1641 if ( is_single_column ) {
1642 int line_flags = 0;
1643 // Honor widows and orphans
1644 if (orphans > 1 && i > 0 && i < orphans)
1645 line_flags |= RN_SPLIT_BEFORE_AVOID;
1646 if (widows > 1 && i < count-1 && count-1 - i < widows)
1647 line_flags |= RN_SPLIT_AFTER_AVOID;
1648 // Honor line's own flags
1649 if (line->flags & LTEXT_LINE_SPLIT_AVOID_BEFORE)
1650 line_flags |= RN_SPLIT_BEFORE_AVOID;
1651 if (line->flags & LTEXT_LINE_SPLIT_AVOID_AFTER)
1652 line_flags |= RN_SPLIT_AFTER_AVOID;
1653 row->single_col_context->AddLine(padding_top + line->y,
1654 padding_top + line->y + line->height, line_flags);
1655 if (i == count-1) // add bottom padding
1656 row->single_col_context->AddLine(padding_top + line->y + line->height,
1657 padding_top + line->y + line->height + padding_bottom, RN_SPLIT_BEFORE_AVOID);
1658 if ( !elem->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) )
1659 continue;
1660 if ( line->flags & LTEXT_LINE_PARA_IS_RTL )
1661 link_insert_pos = row->single_col_context->getCurrentLinksCount();
1662 else
1663 link_insert_pos = -1; // append
1664 }
1665 for ( int w=0; w<line->word_count; w++ ) { // check link start flag for every word
1666 if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) {
1667 const src_text_fragment_t * src = txform->GetSrcInfo( line->words[w].src_text_index );
1668 if ( src && src->object ) {
1669 ldomNode * node = (ldomNode*)src->object;
1670 ldomNode * parent = node->getParentNode();
1671 while (parent && parent->getNodeId() != el_a)
1672 parent = parent->getParentNode();
1673 if ( parent && parent->hasAttribute(LXML_NS_ANY, attr_href ) ) {
1674 lString32 href = parent->getAttributeValue(LXML_NS_ANY, attr_href );
1675 if ( href.length()>0 && href.at(0)=='#' ) {
1676 href.erase(0,1);
1677 if ( is_single_column )
1678 row->single_col_context->addLink( href, link_insert_pos );
1679 else
1680 row->links.add( href );
1681 }
1682 }
1683 }
1684 }
1685 }
1686 }
1687 }
1688
1689 }
1690 else if ( cell->elem->getRendMethod()!=erm_invisible ) {
1691 // We must use a different context (used by rendering
1692 // functions to record, with context.AddLine(), each
1693 // rendered block's height, to be used for splitting
1694 // blocks among pages, for page-mode display), so that
1695 // sub-renderings (of cells' content) do not add to our
1696 // main context. Their heights will already be accounted
1697 // in their row's height (added to main context below).
1698 // Except when table is a single column, and we can just
1699 // transfer lines to the upper context.
1700 LVRendPageContext * cell_context;
1701 int rend_flags = elem->getDocument()->getRenderBlockRenderingFlags();
1702 if ( is_single_column ) {
1703 row->single_col_context = new LVRendPageContext(NULL, context.getPageHeight());
1704 cell_context = row->single_col_context;
1705 // We want to avoid negative margins (if allowed in global flags) and
1706 // going back the flow y, as the transfered lines would not reflect
1707 // that, and we could get some small mismatches and glitches.
1708 rend_flags &= ~BLOCK_RENDERING_ALLOW_NEGATIVE_COLLAPSED_MARGINS;
1709 }
1710 else {
1711 cell_context = new LVRendPageContext( NULL, context.getPageHeight(), false );
1712 }
1713 // We request renderBlockElement() to give us back the baseline
1714 // of the block as expected for tables
1715 cell->baseline = REQ_BASELINE_FOR_TABLE;
1716 int h = renderBlockElement( *cell_context, cell->elem, 0, 0, cell->width,
1717 0, 0, // no usable left/right overflow outside cell
1718 cell->direction, &cell->baseline, rend_flags);
1719 cell->height = h;
1720 // See above about what we store in cell->adjusted_baseline
1721 if ( cell->valign == 0 ) { // vertical-align: baseline
1722 // We'll use that baseline
1723 cell->adjusted_baseline = cell->baseline;
1724 }
1725 else {
1726 // We need the bottom content edge of what's been rendered.
1727 // We just need to remove this cell bottom padding (we should
1728 // not remove the inner content bottom margins or paddings).
1729 int em = cell->elem->getFont()->getSize();
1730 css_style_ref_t elem_style = cell->elem->getStyle();
1731 int padding_bottom = lengthToPx( elem_style->padding[3], cell->width, em ) + measureBorder(cell->elem,2);
1732 // We'll position that bottom content edge
1733 cell->adjusted_baseline = h - padding_bottom;
1734 }
1735 if ( !is_single_column ) {
1736 // Gather footnotes links accumulated by cell_context
1737 lString32Collection * link_ids = cell_context->getLinkIds();
1738 if (link_ids->length() > 0) {
1739 for ( int n=0; n<link_ids->length(); n++ ) {
1740 row->links.add( link_ids->at(n) );
1741 }
1742 }
1743 delete cell_context;
1744 }
1745 }
1746 // RenderRectAccessor needs to be updated after the call
1747 // to renderBlockElement() which will have setX/setY to (0,0).
1748 // But we're updating them to be in the coordinates of the TR.
1749 RenderRectAccessor fmt( cell->elem );
1750 // TRs padding and border don't apply (see below), so they
1751 // don't add any x/y shift to the cells' positions in the TR
1752 fmt.setX(cell->col->x); // relative to its TR (border_spacing_h is
1753 // already accounted in col->x)
1754 fmt.setY(0); // relative to its TR
1755 fmt.setWidth( cell->width );
1756 fmt.setHeight( cell->height );
1757 fmt.push();
1758 // Some fmt.set* may be updated below
1759 #ifdef DEBUG_TABLE_RENDERING
1760 printf("TABLE: renderCell[%d,%d] w/h: %d/%d\n", j, i, cell->width, cell->height);
1761 #endif
1762 if ( cell->rowspan == 1 ) {
1763 // Only set row height from this cell height if it is rowspan=1
1764 // We'll update rows height from cells with rowspan > 1 just below
1765 if ( row->height < cell->height )
1766 row->height = cell->height;
1767 }
1768 // Set the row baseline from baseline-aligned cells' baselines.
1769 // https://www.w3.org/TR/CSS22/tables.html#height-layout
1770 // "First the cells that are aligned on their baseline are positioned.
1771 // This will establish the baseline of the row"
1772 if ( cell->valign == 0 ) { // only cells with vertical-align: baseline
1773 row_has_baseline_aligned_cells = true;
1774 if ( row->baseline < cell->adjusted_baseline )
1775 row->baseline = cell->adjusted_baseline;
1776 // (cell->adjusted_baseline is cell->baseline)
1777 }
1778 }
1779 }
1780 // Fixup row height and baseline
1781 for (j=0; j<rows[i]->cells.length(); j++) {
1782 CCRTableCell * cell = rows[i]->cells[j];
1783 int y = cell->row->index;
1784 if ( i==y ) { // upper left corner of cell
1785 if ( !row_has_baseline_aligned_cells ) {
1786 // "If a row has no cell box aligned to its baseline,
1787 // the baseline of that row is the bottom content edge
1788 // of the lowest cell in the row."
1789 // We have stored in cell->adjusted_baseline the
1790 // cells bottom content edges.
1791 if ( row->baseline < cell->adjusted_baseline )
1792 row->baseline = cell->adjusted_baseline;
1793 }
1794 else if ( cell->valign == 0 ) {
1795 // Cells with vertical-align: baseline must align with
1796 // the row baseline: this can increase the height of
1797 // a cell, and so the height of the row.
1798 int shift_down = row->baseline - cell->adjusted_baseline;
1799 cell->adjusted_baseline = row->baseline;
1800 // And update row height from this cell height if it is rowspan=1
1801 if ( cell->rowspan == 1 && row->height < cell->height + shift_down )
1802 row->height = cell->height + shift_down;
1803 }
1804 // else cell->adjusted_baseline won't be used
1805 }
1806 }
1807 }
1808
1809 // Update rows heights from multi-row (rowspan > 1) cells height
1810 for (i=0; i<rows.length(); i++) {
1811 //CCRTableRow * row = rows[i];
1812 for (j=0; j<rows[i]->cells.length(); j++) {
1813 CCRTableCell * cell = rows[i]->cells[j];
1814 //int x = cell->col->index;
1815 int y = cell->row->index;
1816 if ( i==y && cell->rowspan>1 ) {
1817 int k;
1818 int total_h = 0;
1819 for ( k=i; k<=i+cell->rowspan-1; k++ ) {
1820 CCRTableRow * row2 = rows[k];
1821 total_h += row2->height;
1822 }
1823 int extra_h = cell->height - total_h;
1824 if ( extra_h>0 ) {
1825 int delta = extra_h / cell->rowspan;
1826 int delta_h = extra_h - delta * cell->rowspan;
1827 for ( k=i; k<=i+cell->rowspan-1; k++ ) {
1828 CCRTableRow * row2 = rows[k];
1829 row2->height += delta;
1830 if ( delta_h > 0 ) {
1831 row2->height++;
1832 delta_h--;
1833 }
1834 }
1835 }
1836 }
1837 }
1838 }
1839 if ( enhanced_rendering ) {
1840 // Update rows' bottom overflow to include the height of
1841 // the next rows spanned over by cells with rowspan>1.
1842 // (This must be done in another loop from the one above)
1843 for (i=0; i<rows.length(); i++) {
1844 CCRTableRow * row = rows[i];
1845 int max_h = 0;
1846 for (j=0; j<rows[i]->cells.length(); j++) {
1847 CCRTableCell * cell = rows[i]->cells[j];
1848 int y = cell->row->index;
1849 if ( i==y && cell->rowspan>1 ) {
1850 int total_h = 0;
1851 for ( int k=i; k<=i+cell->rowspan-1; k++ ) {
1852 CCRTableRow * row2 = rows[k];
1853 total_h += row2->height;
1854 }
1855 if ( total_h > max_h ) {
1856 max_h = total_h;
1857 }
1858 }
1859 }
1860 if ( max_h > row->height ) {
1861 row->bottom_overflow = max_h - row->height;
1862 }
1863 }
1864 }
1865
1866 // update rows y and total height
1867 //
1868 // Notes:
1869 // TR are not supposed to have margin or padding according
1870 // to CSS 2.1 https://www.w3.org/TR/CSS21/box.html (they could
1871 // in CSS 2, and may be in CSS 3, not clear), and Firefox ignores
1872 // them too (no effet whatever value, with border-collapse or not).
1873 // (If we have to support them, we could account for their top
1874 // and bottom values here, but the left and right values would
1875 // need to be accounted above while computing assignable_width for
1876 // columns content. Given that each row can be styled differently
1877 // with classNames, we may have different values for each row,
1878 // which would make computing the assignable_width very tedious.)
1879 //
1880 // TR can have borders, but, tested on Firefox with various
1881 // table styles:
1882 // - they are never displayed when NOT border-collapse
1883 // - with border-collapse, they are only displayed when
1884 // the border is greater than the TD ones, so when
1885 // collapsing is applied.
1886 // So, we don't need to account for TR borders here either:
1887 // we collapsed them to the cell if border-collapse,
1888 // and we can just ignore them here, and not draw them in
1889 // DrawDocument() (Former crengine code did draw the border,
1890 // but it drew it OVER the cell content, for lack of accounting
1891 // it in cells placement and content width.)
1892 for (i=0; i<nb_rows; i++) {
1893 table_h += borderspacing_v_top;
1894 CCRTableRow * row = rows[i];
1895 row->y = table_h;
1896 // It can happen there is a row that does not map to
1897 // a node (some are added at start of PlaceCells()),
1898 // so check for row->elem to avoid a segfault
1899 if ( row->elem ) {
1900 RenderRectAccessor fmt( row->elem );
1901 // TR position relative to the TABLE. If it is contained in a table group
1902 // (thead, tbody...), these will be adjusted below to be relative to it.
1903 // (Here were previously added row->elem borders)
1904 fmt.setX(table_border_left + table_padding_left);
1905 fmt.setY(row->y);
1906 fmt.setWidth( table_width - table_border_left - table_padding_left - table_padding_right - table_border_right );
1907 fmt.setHeight( row->height );
1908 // This baseline will only be useful if we're part of
1909 // some flow rendered with REQ_BASELINE_FOR_TABLE
1910 fmt.setBaseline( row->baseline );
1911 if ( enhanced_rendering ) {
1912 fmt.setBottomOverflow( row->bottom_overflow );
1913 }
1914 }
1915 if ( context.wantsLines() && is_single_column ) {
1916 // Transfer lines from each row->single_col_context to main context
1917 // (This has to be done before we update table_h)
1918 int cur_y = table_y0 + table_h;
1919 int line_flags = 0;
1920 if (avoid_pb_inside) {
1921 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1922 }
1923 if (is_rtl)
1924 line_flags |= RN_LINE_IS_RTL;
1925 int content_line_flags = line_flags;
1926 if ( row->height < min_row_height_for_split_by_line ) {
1927 // Too small row height: stick all line together to prevent split
1928 // inside this row
1929 content_line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1930 }
1931 // Add border spacing top
1932 int top_line_flags = line_flags | RN_SPLIT_AFTER_AVOID;
1933 if (i == 0) // first row (or single row): stick to table top padding/border
1934 top_line_flags |= RN_SPLIT_BEFORE_AVOID;
1935 context.AddLine(last_y, cur_y, top_line_flags);
1936 // Add cell lines
1937 if ( row->single_col_context && row->single_col_context->getLines() ) {
1938 // (It could happen no context was created or no line were added, if
1939 // cell was erm_invisible)
1940 LVPtrVector<LVRendLineInfo> * lines = row->single_col_context->getLines();
1941 for ( int i=0; i < lines->length(); i++ ) {
1942 LVRendLineInfo * line = lines->get(i);
1943 context.AddLine(cur_y, cur_y+line->getHeight(), line->getFlags()|content_line_flags);
1944 LVFootNoteList * links = line->getLinks();
1945 if ( links ) {
1946 for ( int j=0; j < links->length(); j++ ) {
1947 context.addLink( links->get(j)->getId() );
1948 }
1949 }
1950 cur_y += line->getHeight();
1951 }
1952 }
1953 // Add border spacing bottom
1954 int bottom_line_flags = line_flags | RN_SPLIT_BEFORE_AVOID;
1955 if (i == nb_rows-1) // last row (or single row): stick to table bottom padding/border
1956 bottom_line_flags |= RN_SPLIT_AFTER_AVOID;
1957 context.AddLine(cur_y, cur_y+borderspacing_v_bottom, bottom_line_flags|RN_SPLIT_BEFORE_AVOID);
1958 last_y = cur_y + borderspacing_v_bottom;
1959 if (last_y != table_y0 + table_h + row->height + borderspacing_v_bottom) {
1960 printf("CRE WARNING: single column table row height error %d =! %d\n",
1961 last_y, table_y0 + table_h + row->height + borderspacing_v_bottom);
1962 }
1963 }
1964 table_h += row->height;
1965 table_h += borderspacing_v_bottom;
1966 if ( context.wantsLines() && !is_single_column ) {
1967 // Includes the row and half of its border_spacing above and half below.
1968 if (avoid_pb_inside) {
1969 // Avoid any split between rows
1970 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1971 }
1972 else if (i == 0) { // first row (or single row)
1973 // Avoid a split between table top border/padding/caption and first row.
1974 // Also, the first row could be column headers: avoid a split between it
1975 // and the 2nd row. (Any other reason to do that?)
1976 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1977 // Former code had:
1978 // line_flags |= CssPageBreak2Flags(getPageBreakBefore(elem))<<RN_SPLIT_BEFORE;
1979 }
1980 else if ( i==nb_rows-1 ) { // last row
1981 // Avoid a split between last row and previous to last (really?)
1982 // Avoid a split between last row and table bottom padding/border
1983 // line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
1984 // Let's not avoid a split between last and previous last, as
1985 // the last row is most often not a bottom TH, and it would just
1986 // drag them onto next page, leaving a hole on previous page.
1987 line_flags = RN_SPLIT_BEFORE_AUTO | RN_SPLIT_AFTER_AVOID;
1988 }
1989 else {
1990 // Otherwise, allow any split between rows, except if
1991 // the rows has some bottom overflow, which means it has
1992 // some cells with rowspan>1 that we'd rather not have cut.
1993 if ( row->bottom_overflow > 0 )
1994 line_flags = RN_SPLIT_BEFORE_AUTO | RN_SPLIT_AFTER_AVOID;
1995 else
1996 line_flags = RN_SPLIT_BEFORE_AUTO | RN_SPLIT_AFTER_AUTO;
1997 }
1998 if (is_rtl)
1999 line_flags |= RN_LINE_IS_RTL;
2000 context.AddLine(last_y, table_y0 + table_h, line_flags);
2001 last_y = table_y0 + table_h;
2002 }
2003 // Add links gathered from this row's cells (even if ! context.wantsLines())
2004 // in case of imbricated tables)
2005 if (row->links.length() > 0) {
2006 for ( int n=0; n<row->links.length(); n++ ) {
2007 context.addLink( row->links[n] );
2008 }
2009 }
2010 }
2011 if (nb_rows > 0) {
2012 // There must be the full borderspacing_v below last row.
2013 // Includes the last half of it here, as the other half was added
2014 // above with the row.
2015 table_h += borderspacing_v_top;
2016 }
2017 table_h += table_padding_bottom + table_border_bottom;
2018 if ( context.wantsLines() ) {
2019 // Any table->style->page-break-after AVOID or ALWAYS will be taken
2020 // care of by renderBlockElement(), so we can use AVOID here.
2021 if ( !enhanced_rendering )
2022 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
2023 else
2024 // but not if called from RenderBlockElementEnhanced, where
2025 // margins handle page splitting a bit differently
2026 line_flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AUTO;
2027 if (is_rtl)
2028 line_flags |= RN_LINE_IS_RTL;
2029 context.AddLine(last_y, table_y0 + table_h, line_flags);
2030 last_y = table_y0 + table_h; // not read after here
2031 (void)last_y; // silences clang warning
2032 }
2033
2034 // Update each cell height to be its row height, so it can draw its
2035 // bottom border where it should be: as the row border.
2036 // We also apply table cells' vertical-align property.
2037 for (i=0; i<rows.length(); i++) {
2038 for (j=0; j<rows[i]->cells.length(); j++) {
2039 CCRTableCell * cell = rows[i]->cells[j];
2040 //int x = cell->col->index;
2041 int y = cell->row->index;
2042 if ( i==y ) {
2043 RenderRectAccessor fmt( cell->elem );
2044 CCRTableRow * lastrow = rows[ cell->row->index + cell->rowspan - 1 ];
2045 int row_h = lastrow->y + lastrow->height - cell->row->y;
2046 // Implement CSS property vertical-align for table cells
2047 // We have to format the cell with the row height for the borders
2048 // to be drawn at the correct positions: we can't just use
2049 // fmt.setY(fmt.getY() + pad) below to implement vertical-align.
2050 // We have to shift down the cell content itself
2051 int cell_h = fmt.getHeight(); // original height that fit cell content
2052 fmt.setHeight( row_h );
2053 if ( cell_h < row_h ) {
2054 int pad = 0; // default when cell->valign=1 / top
2055 if (cell->valign == 0) // baseline
2056 pad = cell->adjusted_baseline - cell->baseline; // shift-down to align cell and row baselines
2057 else if (cell->valign == 2) // center
2058 pad = (row_h - cell_h)/2;
2059 else if (cell->valign == 3) // bottom
2060 pad = (row_h - cell_h);
2061 if ( pad == 0 ) // No need to update this cell
2062 continue;
2063 if ( cell->elem->getRendMethod() == erm_final ) {
2064 if ( enhanced_rendering ) {
2065 // Just shift down the content box
2066 fmt.setInnerY( fmt.getInnerY() + pad );
2067 }
2068 else {
2069 // We need to update the cell element padding-top to include this pad
2070 css_style_ref_t style = cell->elem->getStyle();
2071 css_style_ref_t newstyle(new css_style_rec_t);
2072 copystyle(style, newstyle);
2073 // If padding-top is a percentage, it is relative to
2074 // the *width* of the containing block
2075 int em = cell->elem->getFont()->getSize();
2076 int orig_padding_top = lengthToPx( style->padding[2], cell->width, em );
2077 newstyle->padding[2].type = css_val_screen_px;
2078 newstyle->padding[2].value = orig_padding_top + pad;
2079 cell->elem->setStyle(newstyle);
2080 }
2081 } else if ( cell->elem->getRendMethod() != erm_invisible ) { // erm_block
2082 // We need to update each child fmt.y to include this pad
2083 for (int i=0; i<cell->elem->getChildCount(); i++) {
2084 ldomNode * item = cell->elem->getChildElementNode(i);
2085 if ( item ) {
2086 RenderRectAccessor f( item );
2087 f.setY( f.getY() + pad );
2088 }
2089 }
2090 }
2091 }
2092 }
2093 }
2094 }
2095
2096 // Update row groups (thead, tbody...) placement (we need to do that as
2097 // these rowgroup elements are just block containers of the row elements,
2098 // and they will be navigated by DrawDocument() to draw each child
2099 // relative to its container: RenderRectAccessor X & Y are relative to
2100 // the parent container top left corner)
2101 //
2102 // As mentioned above, rowgroups' margins and paddings should be
2103 // ignored, and their borders are only used with border-collapse,
2104 // and we collapsed them to the cells when we had to.
2105 // So, we ignore them here, and DrawDocument() will NOT draw their
2106 // border.
2107 for ( int i=0; i<rowgroups.length(); i++ ) {
2108 CCRTableRowGroup * grp = rowgroups[i];
2109 if ( grp->rows.length() > 0 ) {
2110 int y0 = grp->rows.first()->y;
2111 int y1 = grp->rows.last()->y + grp->rows.last()->height;
2112 RenderRectAccessor fmt( grp->elem );
2113 fmt.setY( y0 );
2114 fmt.setHeight( y1 - y0 );
2115 fmt.setX( 0 );
2116 fmt.setWidth( table_width );
2117 int max_row_bottom_overflow_y = 0;
2118 for ( int j=0; j<grp->rows.length(); j++ ) {
2119 // make row Y position relative to group
2120 RenderRectAccessor rowfmt( grp->rows[j]->elem );
2121 rowfmt.setY( rowfmt.getY() - y0 );
2122 if ( enhanced_rendering ) {
2123 // max y (relative to y0) from rows with bottom overflow
2124 int row_bottom_overflow_y = rowfmt.getY() + rowfmt.getHeight() + rowfmt.getBottomOverflow();
2125 if (row_bottom_overflow_y > max_row_bottom_overflow_y)
2126 max_row_bottom_overflow_y = row_bottom_overflow_y;
2127 }
2128 }
2129 if ( enhanced_rendering ) {
2130 // Update row group bottom overflow from the rows max
2131 if ( max_row_bottom_overflow_y > fmt.getHeight() ) {
2132 fmt.setBottomOverflow( max_row_bottom_overflow_y - fmt.getHeight() );
2133 }
2134 }
2135 }
2136 }
2137
2138 if ( is_single_column ) {
2139 // Cleanup rows' LVRendPageContext
2140 for ( i=0; i<rows.length(); i++ ) {
2141 if ( rows[i]->single_col_context ) {
2142 delete rows[i]->single_col_context;
2143 }
2144 }
2145 }
2146
2147 return table_h;
2148 }
2149
CCRTable(ldomNode * tbl_elem,int tbl_width,bool tbl_shrink_to_fit,int tbl_direction,bool tbl_avoid_pb_inside,bool tbl_enhanced_rendering,int dwidth,bool tbl_is_ruby_table)2150 CCRTable(ldomNode * tbl_elem, int tbl_width, bool tbl_shrink_to_fit, int tbl_direction, bool tbl_avoid_pb_inside,
2151 bool tbl_enhanced_rendering, int dwidth, bool tbl_is_ruby_table) : digitwidth(dwidth) {
2152 currentRowGroup = NULL;
2153 caption = NULL;
2154 caption_h = 0;
2155 caption_direction = REND_DIRECTION_UNSET;
2156 elem = tbl_elem;
2157 table_width = tbl_width;
2158 shrink_to_fit = tbl_shrink_to_fit;
2159 direction = tbl_direction;
2160 is_rtl = direction == REND_DIRECTION_RTL;
2161 avoid_pb_inside = tbl_avoid_pb_inside;
2162 enhanced_rendering = tbl_enhanced_rendering;
2163 is_ruby_table = tbl_is_ruby_table;
2164 rows_rendering_reordered = false;
2165 #ifdef DEBUG_TABLE_RENDERING
2166 printf("TABLE: ============ parsing new table %s\n",
2167 UnicodeToLocal(ldomXPointer(elem, 0).toString()).c_str());
2168 #endif
2169 LookupElem( tbl_elem, direction, 0 );
2170 FixRowGroupsOrder();
2171 if ( is_ruby_table && rows.length() >= 2 ) {
2172 // Move 2nd row (first ruby annotation) to 1st position,
2173 // so base ruby text (initially 1st row) becomes 2nd
2174 rows.move(0, 1);
2175 rows_rendering_reordered = true;
2176 }
2177 PlaceCells();
2178 if ( enhanced_rendering && rows_rendering_reordered ) {
2179 // printf("table rows re-ordered: %s\n", UnicodeToLocal(ldomXPointer(elem, 0).toString()).c_str());
2180 RenderRectAccessor fmt( elem );
2181 RENDER_RECT_SET_FLAG(fmt, CHILDREN_RENDERING_REORDERED);
2182 if ( !is_ruby_table ) { // don't show this warning as it's expected with ruby
2183 elem->getDocument()->printWarning("table rows/thead/tfoot re-ordered", 2);
2184 }
2185 }
2186 }
2187 };
2188
renderTable(LVRendPageContext & context,ldomNode * node,int x,int y,int width,bool shrink_to_fit,int & fitted_width,int direction,bool avoid_pb_inside,bool enhanced_rendering,bool is_ruby_table)2189 int renderTable( LVRendPageContext & context, ldomNode * node, int x, int y, int width, bool shrink_to_fit,
2190 int & fitted_width, int direction, bool avoid_pb_inside, bool enhanced_rendering, bool is_ruby_table )
2191 {
2192 CR_UNUSED2(x, y);
2193 CCRTable table( node, width, shrink_to_fit, direction, avoid_pb_inside, enhanced_rendering, 10, is_ruby_table );
2194 int h = table.renderCells( context );
2195 if (shrink_to_fit)
2196 fitted_width = table.table_width;
2197 return h;
2198 }
2199
freeFormatData(ldomNode * node)2200 void freeFormatData( ldomNode * node )
2201 {
2202 node->clearRenderData();
2203 }
2204
isSameFontStyle(css_style_rec_t * style1,css_style_rec_t * style2)2205 bool isSameFontStyle( css_style_rec_t * style1, css_style_rec_t * style2 )
2206 {
2207 return (style1->font_family == style2->font_family)
2208 && (style1->font_size == style2->font_size)
2209 && (style1->font_style == style2->font_style)
2210 && (style1->font_name == style2->font_name)
2211 && (style1->font_weight == style2->font_weight);
2212 }
2213
2214 //int rend_font_embolden = STYLE_FONT_EMBOLD_MODE_EMBOLD;
2215 int rend_font_embolden = STYLE_FONT_EMBOLD_MODE_NORMAL;
2216
LVRendSetFontEmbolden(int addWidth)2217 void LVRendSetFontEmbolden( int addWidth )
2218 {
2219 if ( addWidth < 0 )
2220 addWidth = 0;
2221 else if ( addWidth>STYLE_FONT_EMBOLD_MODE_EMBOLD )
2222 addWidth = STYLE_FONT_EMBOLD_MODE_EMBOLD;
2223
2224 rend_font_embolden = addWidth;
2225 }
2226
LVRendGetFontEmbolden()2227 int LVRendGetFontEmbolden()
2228 {
2229 return rend_font_embolden;
2230 }
2231
getFont(css_style_rec_t * style,int documentId)2232 LVFontRef getFont(css_style_rec_t * style, int documentId)
2233 {
2234 int sz;
2235 if ( style->font_size.type == css_val_em || style->font_size.type == css_val_ex ||
2236 style->font_size.type == css_val_percent ) {
2237 // font_size.type can't be em/ex/%, it should have been converted to px
2238 // or screen_px while in setNodeStyle().
2239 printf("CRE WARNING: getFont: %d of unit %d\n", style->font_size.value>>8, style->font_size.type);
2240 sz = style->font_size.value >> 8; // set some value anyway
2241 }
2242 else {
2243 // We still need to convert other absolute units to px.
2244 // (we pass 0 as base_em and base_px, as these would not be used).
2245 sz = lengthToPx(style->font_size, 0, 0);
2246 }
2247 if ( sz < 8 )
2248 sz = 8;
2249 if ( sz > 340 )
2250 sz = 340;
2251 int fw;
2252 if (style->font_weight>=css_fw_100 && style->font_weight<=css_fw_900)
2253 fw = ((style->font_weight - css_fw_100)+1) * 100;
2254 else
2255 fw = 400;
2256 fw += rend_font_embolden;
2257 if ( fw>900 )
2258 fw = 900;
2259 // printf("cssd_font_family: %d %s", style->font_family, style->font_name.c_str());
2260 LVFontRef fnt = fontMan->GetFont(
2261 sz,
2262 fw,
2263 style->font_style==css_fs_italic,
2264 style->font_family,
2265 lString8(style->font_name.c_str()),
2266 style->font_features.value, // (.type is always css_val_unspecified after setNodeStyle())
2267 documentId, true); // useBias=true, so that our preferred font gets used
2268 //fnt = LVCreateFontTransform( fnt, LVFONT_TRANSFORM_EMBOLDEN );
2269 return fnt;
2270 }
2271
styleToTextFmtFlags(bool is_block,const css_style_ref_t & style,lUInt32 oldflags,int direction)2272 lUInt32 styleToTextFmtFlags( bool is_block, const css_style_ref_t & style, lUInt32 oldflags, int direction )
2273 {
2274 lUInt32 flg = oldflags;
2275 if (style.isNull())
2276 return flg;
2277 if ( is_block ) {
2278 // text alignment flags
2279 flg = oldflags & ~(LTEXT_FLAG_NEWLINE | (LTEXT_FLAG_NEWLINE<<LTEXT_LAST_LINE_ALIGN_SHIFT) | LTEXT_LAST_LINE_IF_NOT_FIRST);
2280 switch (style->text_align)
2281 {
2282 case css_ta_left:
2283 flg |= LTEXT_ALIGN_LEFT;
2284 break;
2285 case css_ta_right:
2286 flg |= LTEXT_ALIGN_RIGHT;
2287 break;
2288 case css_ta_center:
2289 flg |= LTEXT_ALIGN_CENTER;
2290 break;
2291 case css_ta_justify:
2292 flg |= LTEXT_ALIGN_WIDTH;
2293 break;
2294 case css_ta_start:
2295 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_ALIGN_RIGHT : LTEXT_ALIGN_LEFT);
2296 break;
2297 case css_ta_end:
2298 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_ALIGN_LEFT : LTEXT_ALIGN_RIGHT);
2299 break;
2300 case css_ta_inherit:
2301 default: // others values shouldn't happen (only accepted with text-align-last)
2302 break;
2303 }
2304 switch (style->text_align_last)
2305 {
2306 case css_ta_left:
2307 flg |= LTEXT_LAST_LINE_ALIGN_LEFT;
2308 break;
2309 case css_ta_right:
2310 flg |= LTEXT_LAST_LINE_ALIGN_RIGHT;
2311 break;
2312 case css_ta_center:
2313 flg |= LTEXT_LAST_LINE_ALIGN_CENTER;
2314 break;
2315 case css_ta_justify:
2316 flg |= LTEXT_LAST_LINE_ALIGN_WIDTH;
2317 break;
2318 case css_ta_start:
2319 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_LAST_LINE_ALIGN_RIGHT : LTEXT_LAST_LINE_ALIGN_LEFT);
2320 break;
2321 case css_ta_end:
2322 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_LAST_LINE_ALIGN_LEFT : LTEXT_LAST_LINE_ALIGN_RIGHT);
2323 break;
2324 case css_ta_auto: // let flg have none of the above set, which will mean "auto"
2325 case css_ta_inherit:
2326 break;
2327 case css_ta_left_if_not_first: // Private text-align-last keywords
2328 flg |= LTEXT_LAST_LINE_ALIGN_LEFT;
2329 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2330 break;
2331 case css_ta_right_if_not_first:
2332 flg |= LTEXT_LAST_LINE_ALIGN_RIGHT;
2333 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2334 break;
2335 case css_ta_center_if_not_first:
2336 flg |= LTEXT_LAST_LINE_ALIGN_CENTER;
2337 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2338 break;
2339 case css_ta_justify_if_not_first:
2340 flg |= LTEXT_LAST_LINE_ALIGN_WIDTH;
2341 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2342 break;
2343 case css_ta_start_if_not_first:
2344 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_LAST_LINE_ALIGN_RIGHT : LTEXT_LAST_LINE_ALIGN_LEFT);
2345 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2346 break;
2347 case css_ta_end_if_not_first:
2348 flg |= (direction == REND_DIRECTION_RTL ? LTEXT_LAST_LINE_ALIGN_LEFT : LTEXT_LAST_LINE_ALIGN_RIGHT);
2349 flg |= LTEXT_LAST_LINE_IF_NOT_FIRST;
2350 break;
2351 }
2352 }
2353 // We should clean these flags that we got from the parent node via baseFlags:
2354 // CSS white-space inheritance is correctly handled via styles (so, no need
2355 // for this alternative way to ensure inheritance with flags), but might have
2356 // been cancelled and set to some other value (e.g.: normal inside pre)
2357 flg &= ~(LTEXT_FLAG_PREFORMATTED|LTEXT_FLAG_NOWRAP);
2358 if ( style->white_space >= css_ws_pre ) // white-space: pre, pre-wrap, break-spaces
2359 flg |= LTEXT_FLAG_PREFORMATTED;
2360 if ( style->white_space == css_ws_nowrap ) // white-space: nowrap
2361 flg |= LTEXT_FLAG_NOWRAP;
2362 if ( STYLE_HAS_CR_HINT(style, FIT_GLYPHS) ) // glyph fitting via -cr-hint
2363 flg |= LTEXT_FIT_GLYPHS;
2364 return flg;
2365 }
2366
2367 // Convert CSS value (type + number value) to screen px
lengthToPx(css_length_t val,int base_px,int base_em,bool unspecified_as_em)2368 int lengthToPx( css_length_t val, int base_px, int base_em, bool unspecified_as_em )
2369 {
2370 if (val.type == css_val_screen_px) { // use value as is
2371 return val.value;
2372 }
2373
2374 // base_px is usually the width of the container element
2375 // base_em is usually the font size of the parent element
2376 int px = 0; // returned screen px
2377 bool ensure_non_zero = true; // return at least 1px if val.value is non-zero
2378 // Previously, we didn't, so don't ensure non-zero if gRenderDPI=0
2379 if (!gRenderDPI) ensure_non_zero = false;
2380
2381 // Scale style value according to gRenderDPI (no scale if 96 or 0)
2382 // we do that early to not lose precision
2383 int value = scaleForRenderDPI(val.value);
2384
2385 css_value_type_t type = val.type;
2386 if (unspecified_as_em && type == css_val_unspecified)
2387 type = css_val_em;
2388
2389 // value for all units is stored *256 to not lose fractional part
2390 switch( type )
2391 {
2392 /* absolute value, most often seen */
2393 case css_val_px:
2394 // round it to closest int
2395 px = (value + 0x7F) >> 8;
2396 break;
2397
2398 /* relative values */
2399 /* We should use val.value (not scaled by gRenderDPI) here */
2400 case css_val_em: // value = em*256 (font size of the current element)
2401 px = (base_em * val.value) >> 8;
2402 break;
2403 case css_val_percent:
2404 px = ( base_px * val.value / 100 ) >> 8;
2405 break;
2406 case css_val_ex: // value = ex*512 (approximated with base_em, 1ex =~ 0.5em in many fonts)
2407 px = (base_em * val.value) >> 9;
2408 break;
2409
2410 case css_val_rem: // value = rem*256 (font size of the root element)
2411 px = (gRootFontSize * val.value) >> 8;
2412 break;
2413
2414 /* absolute value, less often used - value = unit*256 */
2415 /* (previously treated by crengine as 0, which we still do if gRenderDPI=0) */
2416 case css_val_in: // 2.54 cm 1in = 96px
2417 if (gRenderDPI)
2418 px = (96 * value ) >> 8;
2419 break;
2420 case css_val_cm: // 2.54cm = 96px
2421 if (gRenderDPI)
2422 px = (int)(96 * value / 2.54) >> 8;
2423 break;
2424 case css_val_mm: // 25.4mm = 96px
2425 if (gRenderDPI)
2426 px = (int)(96 * value / 25.4) >> 8;
2427 break;
2428 case css_val_pt: // 1/72 in 72pt = 96px
2429 if (gRenderDPI)
2430 px = 96 * value / 72 >> 8;
2431 break;
2432 case css_val_pc: // 12 pt 6pc = 96px
2433 if (gRenderDPI)
2434 px = 96 * value / 6 >> 8;
2435 break;
2436
2437 case css_val_unspecified: // may be used with named values like "auto", but should
2438 case css_val_inherited: // return 0 when lengthToPx() is called on them
2439 default:
2440 px = 0;
2441 ensure_non_zero = false;
2442 }
2443 if (!px && val.value && ensure_non_zero)
2444 px = 1;
2445 return px;
2446 }
2447
SplitLines(const lString32 & str,lString32Collection & lines)2448 void SplitLines( const lString32 & str, lString32Collection & lines )
2449 {
2450 const lChar32 * s = str.c_str();
2451 const lChar32 * start = s;
2452 for ( ; *s; s++ ) {
2453 if ( *s=='\r' || *s=='\n' ) {
2454 //if ( s > start )
2455 // lines.add( cs32("*") + lString32( start, s-start ) + cs32("<") );
2456 //else
2457 // lines.add( cs32("#") );
2458 if ( (s[1] =='\r' || s[1]=='\n') && (s[1]!=s[0]) )
2459 s++;
2460 start = s+1;
2461 }
2462 }
2463 while ( *start=='\r' || *start=='\n' )
2464 start++;
2465 if ( s > start )
2466 lines.add( lString32( start, (lvsize_t)(s-start) ) );
2467 }
2468
2469 // Returns the marker for a list item node. If txform is supplied render the marker, too.
2470 // marker_width is updated and can be used to add indent or padding necessary to make
2471 // room for the marker (what and how to do it depending of list-style_position (inside/outside)
2472 // is left to the caller)
renderListItemMarker(ldomNode * enode,int & marker_width,LFormattedText * txform,int line_h,lUInt32 flags)2473 lString32 renderListItemMarker( ldomNode * enode, int & marker_width, LFormattedText * txform, int line_h, lUInt32 flags ) {
2474 lString32 marker;
2475 marker_width = 0;
2476 ldomDocument* doc = enode->getDocument();
2477 // The UL > LI parent-child chain may have had some of our boxing elements inserted
2478 ldomNode * parent = enode->getUnboxedParent();
2479 ListNumberingPropsRef listProps = doc->getNodeNumberingProps( parent->getDataIndex() );
2480 if ( listProps.isNull() ) { // no previously cached info: compute and cache it
2481 // Scan all our siblings to know the widest marker width
2482 int counterValue = 0;
2483 int maxWidth = 0;
2484 ldomNode * sibling = parent->getUnboxedFirstChild(true);
2485 while ( sibling ) {
2486 lString32 marker;
2487 int markerWidth = 0;
2488 if ( sibling->getNodeListMarker( counterValue, marker, markerWidth ) ) {
2489 if ( markerWidth > maxWidth )
2490 maxWidth = markerWidth;
2491 }
2492 sibling = sibling->getUnboxedNextSibling(true); // skip text nodes
2493 }
2494 listProps = ListNumberingPropsRef( new ListNumberingProps(counterValue, maxWidth) );
2495 doc->setNodeNumberingProps( parent->getDataIndex(), listProps );
2496 }
2497 // Note: node->getNodeListMarker() uses font->getTextWidth() without any hint about
2498 // text direction, so the marker is measured LTR.. We should probably upgrade them
2499 // to measureText() with the right direction, to get a correct marker_width...
2500 // For now, as node->getNodeListMarker() adds some whitespace and padding, we should
2501 // be fine with any small error due to different measuring with LTR vs RTL.
2502 int counterValue = 0;
2503 if ( enode->getNodeListMarker( counterValue, marker, marker_width ) ) {
2504 if ( !listProps.isNull() )
2505 marker_width = listProps->maxWidth;
2506 css_style_ref_t style = enode->getStyle();
2507 LVFontRef font = enode->getFont();
2508 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
2509 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
2510 if (line_h < 0) { // -1, not specified by caller: find it out from the node
2511 if ( style->line_height.type == css_val_unspecified &&
2512 style->line_height.value == css_generic_normal ) {
2513 line_h = font->getHeight(); // line-height: normal
2514 }
2515 else {
2516 int em = font->getSize();
2517 line_h = lengthToPx(style->line_height, em, em, true);
2518 }
2519 // Scale line_h according to document's _interlineScaleFactor
2520 if (style->line_height.type != css_val_screen_px && doc->getInterlineScaleFactor() != INTERLINE_SCALE_FACTOR_NO_SCALE)
2521 line_h = (line_h * doc->getInterlineScaleFactor()) >> INTERLINE_SCALE_FACTOR_SHIFT;
2522 if ( STYLE_HAS_CR_HINT(style, STRUT_CONFINED) )
2523 flags |= LTEXT_STRUT_CONFINED;
2524 }
2525 marker += "\t";
2526 // That "\t" had some purpose in css_d_list_item_legacy rendering to mark the end
2527 // of the marker, and by providing the marker_width as negative indent, so that
2528 // the following text can have some constant indent by rendering it just like
2529 // negative/hanging text-indent. It has no real use if we provide a 0-indent
2530 // like we do below.
2531 // But coincidentally, this "\t" acts for fribidi as a text segment separator (SS)
2532 // which will bidi-isolate the marker from the followup text, and will ensure,
2533 // for example, that:
2534 // <li style="list-style-type: lower-roman; list-style-type: inside">Some text</li>
2535 // in a RTL direction context, will be rightly rendered as:
2536 // "Some text xviii"
2537 // and not wrongly (like it would if we were to use a space instead of \t):
2538 // "xviii Some text"
2539 // (the "xviii" marker will be in its own LTR segment, and the followup text
2540 // in another LTR segment)
2541 if ( txform ) {
2542 TextLangCfg * lang_cfg = TextLangMan::getTextLangCfg( enode );
2543 txform->AddSourceLine( marker.c_str(), marker.length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, 0, 0);
2544 }
2545 }
2546 return marker;
2547 }
2548
2549 // (Common condition used at multiple occasions, made as as function for clarity)
renderAsListStylePositionInside(const css_style_ref_t style,bool is_rtl=false)2550 bool renderAsListStylePositionInside( const css_style_ref_t style, bool is_rtl=false ) {
2551 bool render_as_lsp_inside = false;
2552 if ( style->list_style_position == css_lsp_inside ) {
2553 return true;
2554 }
2555 else if ( style->list_style_position == css_lsp_outside ) {
2556 // Rendering hack: we do that too when list-style-position = outside AND
2557 // (with LTR) text-align "right" or "center", as this will draw the marker
2558 // at the near left of the text (otherwise, the marker would be drawn on
2559 // the far left of the whole available width, which is ugly)
2560 css_text_align_t ta = style->text_align;
2561 if ( ta == css_ta_end ) {
2562 return true;
2563 }
2564 else if ( is_rtl ) {
2565 if (ta == css_ta_center || ta == css_ta_left )
2566 return true;
2567 }
2568 else {
2569 if (ta == css_ta_center || ta == css_ta_right )
2570 return true;
2571 }
2572 }
2573 return false;
2574 }
2575
2576 //=======================================================================
2577 // Render final block
2578 //=======================================================================
2579 // This renderFinalBlock() is NOT the equivalent of renderBlockElement()
2580 // for nodes with erm_final ! But ldomNode::renderFinalBlock() IS.
2581 //
2582 // Here, we just walk all the inline nodes to AddSourceLine() text nodes and
2583 // AddSourceObject() image nodes to the provided LFormattedText * txform.
2584 // It is the similarly named (called by renderBlockElement() when erm_final):
2585 // ldomNode::renderFinalBlock(LFormattedTextRef & frmtext, RenderRectAccessor * fmt, int width)
2586 // that is provided with a width (with padding removed), and after calling
2587 // this 'void renderFinalBlock()' here, calls:
2588 // int h = LFormattedTextRef->Format((lUInt16)width, (lUInt16)page_h)
2589 // to do the actual width-constrained rendering of the AddSource*'ed objects.
2590 // Note: fmt is the RenderRectAccessor of the final block itself, and is passed
2591 // as is to the inline children elements: it is only used to get the width of
2592 // the container, which is only needed to compute indent (text-indent) values in %,
2593 // and to get paragraph direction (LTR/RTL/UNSET).
renderFinalBlock(ldomNode * enode,LFormattedText * txform,RenderRectAccessor * fmt,lUInt32 & baseflags,int indent,int line_h,TextLangCfg * lang_cfg,int valign_dy,bool * is_link_start)2594 void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAccessor * fmt, lUInt32 & baseflags, int indent, int line_h, TextLangCfg * lang_cfg, int valign_dy, bool * is_link_start )
2595 {
2596 bool legacy_rendering = !BLOCK_RENDERING_N(enode, ENHANCED);
2597 if ( enode->isElement() ) {
2598 lvdom_element_render_method rm = enode->getRendMethod();
2599 if ( rm == erm_invisible )
2600 return; // don't draw invisible
2601
2602 if ( enode->hasAttribute( attr_lang ) ) {
2603 lString32 lang_tag = enode->getAttributeValue( attr_lang );
2604 if ( !lang_tag.empty() )
2605 lang_cfg = TextLangMan::getTextLangCfg( lang_tag );
2606 }
2607
2608 if ( enode->isFloatingBox() && rm != erm_final ) {
2609 // (A floating floatBox can't be erm_final: it is always erm_block,
2610 // but let's just be sure of that.)
2611 // If we meet a floatBox here, it's an embedded float (a float
2612 // among other inlines elements). We just add a reference to it
2613 // with AddSourceObject; nothing to do with it until a call
2614 // to LFormattedTextRef->Format(width) where its width will
2615 // be guessed and renderBlockElement() called to render it
2616 // and get is height, so LFormattedText knows how to render
2617 // this erm_final text around it.
2618 txform->AddSourceObject(baseflags|LTEXT_SRC_IS_FLOAT, line_h, valign_dy, indent, enode, lang_cfg );
2619 baseflags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
2620 return;
2621 }
2622
2623 css_style_ref_t style = enode->getStyle();
2624 bool is_object = false;
2625 const css_elem_def_props_t * ntype = enode->getElementTypePtr();
2626 if ( ntype && ntype->is_object )
2627 is_object = true;
2628 // inline-block boxes are handled below quite just like inline images/is_object
2629 bool is_inline_box = enode->isBoxingInlineBox();
2630
2631 int direction = RENDER_RECT_PTR_GET_DIRECTION(fmt);
2632 bool is_rtl = direction == REND_DIRECTION_RTL;
2633
2634 ldomNode * parent = enode->getParentNode(); // Needed for various checks below
2635 if (parent && parent->isNull())
2636 parent = NULL;
2637
2638 // About styleToTextFmtFlags:
2639 // - with inline nodes, it only updates LTEXT_FLAG_PREFORMATTED flag
2640 // when css_ws_pre and LTEXT_FLAG_NOWRAP when css_ws_nowrap.
2641 // - with block nodes (so, only with the first "final" node, and not
2642 // when recursing its children which are inline), it will also set
2643 // horitontal alignment flags.
2644 bool is_block = rm == erm_final;
2645 if (legacy_rendering && !is_block) {
2646 // In legacy rendering mode, we should get the same text formatting flags
2647 // as in CoolReader 3.2.38 and earlier, i.e. set is_block to true for
2648 // any block-like elements as set by CSS.
2649 is_block = style->display >= css_d_block;
2650 if (is_block) {
2651 // With a specific tweak for display:run-in (FB2 footnotes):
2652 // First node with "display: block" after node "display: run-in" in one section
2653 // must be rendered as an inline node.
2654 if ( enode->getNodeIndex() == 1 ) { // we're the 2nd child of parent
2655 ldomNode * first_sibling = parent->getChildNode(0);
2656 if (first_sibling && !first_sibling->isNull() && first_sibling->isElement()) {
2657 css_style_ref_t fs_style = first_sibling->getStyle();
2658 if (!fs_style.isNull() && fs_style->display == css_d_run_in) {
2659 is_block = false;
2660 }
2661 }
2662 }
2663 if ( is_block ) {
2664 // If still block, also check this block is not contained
2665 // in a run-in, in which case we should keep it inline
2666 ldomNode * n = enode;
2667 while ( n && n->getRendMethod() != erm_final ) {
2668 if ( n->getStyle()->display == css_d_run_in ) {
2669 is_block = false;
2670 break;
2671 }
2672 n = n->getParentNode();
2673 }
2674 }
2675 }
2676 }
2677 lUInt32 flags = styleToTextFmtFlags( is_block, style, baseflags, direction );
2678 // Note:
2679 // - baseflags (passed by reference) is shared and re-used by this node's siblings
2680 // (all inline); it should carry newline/horizontal aligment flag, which should
2681 // be cleared when used.
2682 // - flags is provided to this node's children (all inline) (becoming baseflags
2683 // for them), and should carry inherited text decoration, vertical alignment
2684 // and whitespace-pre state.
2685
2686 int width = fmt->getWidth();
2687 int em = enode->getFont()->getSize();
2688
2689 // Nodes with "display: run-in" are inline nodes brought at start of the final node
2690 bool isRunIn = style->display == css_d_run_in;
2691 if ( isRunIn ) {
2692 // The text alignment of the paragraph should come from the following
2693 // sibling node. The one set from the parent final node has probably
2694 // not yet been consumed, so update it.
2695 if ( baseflags & LTEXT_FLAG_NEWLINE ) {
2696 if ( enode->getNodeIndex() == 0 && parent && parent->getChildCount() > 1 ) {
2697 ldomNode * next_sibling = parent->getChildNode(1);
2698 if ( next_sibling && !next_sibling->isNull() && !next_sibling->isElement() ) {
2699 // The next sibling might be a text node, so get the next one
2700 if ( parent->getChildCount() > 2 ) {
2701 next_sibling = parent->getChildNode(2);
2702 }
2703 }
2704 if ( next_sibling && !next_sibling->isNull() && next_sibling->isElement() ) {
2705 // next_sibling is an original block node that should have
2706 // been erm_final, but has been made erm_inline so it can
2707 // be prepended with the run-in node content.
2708 lUInt32 next_sibling_flags = styleToTextFmtFlags( true, next_sibling->getStyle(), baseflags, direction );
2709 // Grab only the alignment flags
2710 lUInt32 align_flags_mask = LTEXT_FLAG_NEWLINE | (LTEXT_FLAG_NEWLINE<<LTEXT_LAST_LINE_ALIGN_SHIFT) | LTEXT_LAST_LINE_IF_NOT_FIRST;
2711 next_sibling_flags &= align_flags_mask;
2712 // Update both flags and baseflags with the grabbed alignments
2713 flags &= ~align_flags_mask;
2714 flags |= next_sibling_flags;
2715 baseflags &= ~align_flags_mask;
2716 baseflags |= next_sibling_flags;
2717 }
2718 }
2719 }
2720 // Note: for consistency, we should also build the strut below from
2721 // this next_sibling node. But let's not bother, display: run-in
2722 // is only really used for FB2 footnotes, and this above is just
2723 // what's needed for their correct rendering.
2724 }
2725
2726 // As seen with Firefox, an inline node line-height: do apply, so we need
2727 // to compute it for all inline nodes, and not only in the "the top and
2728 // single 'final' node" 'if' below. The computed line-height: of that final
2729 // node does ensure the strut height, which is the minimal line-height for
2730 // all inline nodes. But an individual inline node is able to increase that
2731 // strut height, for the line it happens on only.
2732 if (gRenderDPI) {
2733 // line_h is named 'interval' in lvtextfm.cpp.
2734 // Note: it was formerly described as: "*16 (16=normal, 32=double)"
2735 // and the scaling to the font size (font height actually) was done
2736 // in lvtextfm.cpp.
2737 // This has been modified so that lvtextfm only accepts interval as
2738 // being already the final line height in screen pixels.
2739 // So, we only do any conversion from CSS to screen pixels here (and in
2740 // setNodeStyle() when the style->line_height is inherited).
2741 // All related values (%, em, ex, unitless) apply their factor to
2742 // enode->getFont()->getSize().
2743 // Only "normal" uses enode->getFont()->getHeight()
2744 if ( style->line_height.type == css_val_unspecified &&
2745 style->line_height.value == css_generic_normal ) {
2746 line_h = enode->getFont()->getHeight(); // line-height: normal
2747 }
2748 else {
2749 // In all other cases (%, em, unitless/unspecified), we can just scale 'em',
2750 // and use the computed value for absolute sized values (these will
2751 // be affected by gRenderDPI) and 'rem' (related to root element font size).
2752 line_h = lengthToPx(style->line_height, em, em, true);
2753 }
2754 }
2755 else {
2756 // Let's fallback to the previous (wrong) behaviour when gRenderDPI=0
2757 // Only do it for the top and single final node
2758 if ((flags & LTEXT_FLAG_NEWLINE) && rm == erm_final) {
2759 int fh = enode->getFont()->getHeight(); // former code used font height for everything
2760 switch( style->line_height.type ) {
2761 case css_val_percent:
2762 case css_val_em:
2763 line_h = lengthToPx(style->line_height, fh, fh);
2764 break;
2765 default: // Use font height (as line_h=16 in former code)
2766 line_h = fh;
2767 break;
2768 }
2769 }
2770 }
2771 if (line_h < 0) {
2772 // Shouldn't happen, but in case we're called with line_h=-1 and we
2773 // didn't compute a valid line_h:
2774 printf("CRE WARNING: line_h still < 0: using 'normal'\n");
2775 line_h = enode->getFont()->getHeight(); // line-height: normal
2776 }
2777 // having line_h=0 is ugly, but it's allowed and it works
2778
2779 // Scale line_h according to document's _interlineScaleFactor, but
2780 // not if it was already in screen_px, which means it has already
2781 // been scaled (in setNodeStyle() when inherited).
2782 int interline_scale_factor = enode->getDocument()->getInterlineScaleFactor();
2783 if ( style->line_height.type != css_val_screen_px && interline_scale_factor != INTERLINE_SCALE_FACTOR_NO_SCALE ) {
2784 if ( RENDER_RECT_PTR_HAS_FLAG(fmt, NO_INTERLINE_SCALE_UP) && interline_scale_factor > INTERLINE_SCALE_FACTOR_NO_SCALE ) {
2785 // Don't scale up (for <ruby> content, so we can increase interline to make
2786 // the text breath without spreading ruby annotations on the space gained)
2787 }
2788 else {
2789 line_h = (line_h * interline_scale_factor) >> INTERLINE_SCALE_FACTOR_SHIFT;
2790 }
2791 }
2792
2793 if ( (flags & LTEXT_FLAG_NEWLINE) && ( rm == erm_final || ( legacy_rendering && is_block ) ) ) {
2794 // Top and single 'final' node (unless in the degenerate case
2795 // of obsolete css_d_list_item_legacy):
2796 // Get text-indent and line-height that will apply to the full final block
2797 // There is also an exception: in legacy rendering mode, we must also indent any blocks.
2798
2799 // text-indent should really not have to be handled here: it would be
2800 // better handled in ldomNode::renderFinalBlock(), grabbing it from the
2801 // final node, and only passed as an arg to LFormattedText->Format(),
2802 // like we pass to it the text block width.
2803 // Current code passes indent to all txform->AddSource*(.., indent,..), so
2804 // it is stored in each src_text_fragment_t->indent, while it's really
2805 // a property of the whole paragraph, as it is fetched from the top node,
2806 // like we do here. (It is never updated, and as it is not passed by reference,
2807 // updates/reset would not apply to sibling or parent nodes.)
2808 // There is just one case that sets it to a different value: in the
2809 // obsolete css_d_list_item_legacy rendering with lsp_outside, where
2810 // it is set to a negative value (the width of the marker), so to handle text
2811 // indentation from the outside marker just like regular negative text-indent.
2812 // So, sadly, let's keep it that way to not break legacy rendering.
2813 // todo: pass indent via txform->setTextIndent() (like we do for the strut
2814 // below, and get rid of it in AddSourceLine())
2815 indent = lengthToPx(style->text_indent, width, em);
2816 // lvstsheet sets the lowest bit to 1 when text-indent has the "hanging" keyword:
2817 if ( style->text_indent.value & 0x00000001 ) {
2818 // lvtextfm handles negative indent as "indent by the negated (so, then
2819 // positive) value all lines but the first"
2820 indent = -indent;
2821 // We keep real negative values as negative here. They are also handled
2822 // in renderBlockElementEnhanced() to possibly have the text block shifted
2823 // to the left to properly apply the negative effect ("hanging" text-indent
2824 // does not need that).
2825 }
2826
2827 if (rm == erm_final) {
2828 // We set the LFormattedText strut_height and strut_baseline
2829 // with the values from this "final" node. All lines made out from
2830 // children will have a minimal height and baseline set to these.
2831 // See https://www.w3.org/TR/CSS2/visudet.html#line-height
2832 // The minimum height consists of a minimum height above
2833 // the baseline and a minimum depth below it, exactly as if
2834 // each line box starts with a zero-width inline box with the
2835 // element's font and line height properties. We call that
2836 // imaginary box a "strut."
2837 // and https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
2838 int fh = enode->getFont()->getHeight();
2839 int fb = enode->getFont()->getBaseline();
2840 int f_half_leading = (line_h - fh) / 2;
2841 txform->setStrut(line_h, fb + f_half_leading);
2842 }
2843 }
2844 else if ( STYLE_HAS_CR_HINT(style, STRUT_CONFINED) ) {
2845 // Previous branch for the top final node has set the strut.
2846 // Inline nodes having "-cr-hint: strut-confined" will be confined
2847 // inside that strut.
2848 flags |= LTEXT_STRUT_CONFINED;
2849 }
2850
2851 // Now, process styles that may differ between inline nodes, and
2852 // are needed to display any children text node.
2853
2854 // Vertical alignment flags & y-drift from main baseline.
2855 // valign_dy is all that is needed for text nodes, but we need
2856 // a LTEXT_VALIGN_* flag for objects (images), as their height
2857 // is not known here, and only computed in lvtextfm.cpp.
2858 //
2859 // Texts in quotes from https://www.w3.org/TR/CSS2/visudet.html#line-height
2860 //
2861 // We update valign_dy, so it is passed to all children and used
2862 // as a base for their own vertical align computations.
2863 // There are a few vertical-align named values that need a special
2864 // processing for images (their current font is the parent font, and
2865 // of no use for vertical-alignement).
2866 css_length_t vertical_align = style->vertical_align;
2867 if ( (vertical_align.type == css_val_unspecified && vertical_align.value == css_va_baseline) ||
2868 vertical_align.value == 0 ) {
2869 // "Align the baseline of the box with the baseline of the parent box.
2870 // If the box does not have a baseline, align the bottom margin edge with
2871 // the parent's baseline."
2872 // This is the default behaviour in lvtextfm.cpp: no valign_dy or flags
2873 // change needed: keep the existing ones (parent's y drift related to
2874 // the line box main baseline)
2875 }
2876 else {
2877 // We need current and parent nodes font metrics for most computations.
2878 // A few misc notes:
2879 // - Freetype includes any "line gap" from the font metrics
2880 // in the ascender (the part above the baseline).
2881 // - getBaseline() gives the distance from the top to the baseline (so,
2882 // ascender + line gap)
2883 // - the descender font value is added to baseline to make height
2884 // - getHeight() is usually larger than getSize(), and
2885 // getSize() is often nearer to getBaseline().
2886 // See: https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
2887 // Some examples with various fonts at various sizes:
2888 // size=9 height=11 baseline=8
2889 // size=11 height=15 baseline=11
2890 // size=13 height=16 baseline=13
2891 // size=19 height=23 baseline=18
2892 // size=23 height=31 baseline=24
2893 // size=26 height=31 baseline=25
2894 // - Freetype has no function to give us font subscript, superscript, x-height
2895 // and related values, so we have to approximate them from height and baseline.
2896 int fh = enode->getFont()->getHeight();
2897 int fb = enode->getFont()->getBaseline();
2898 int f_line_h = line_h; // computed above
2899 int f_half_leading = (f_line_h - fh) /2;
2900 // Use the current font values if no parent (should not happen thus) to
2901 // avoid the need for if-checks below
2902 int pem = em;
2903 int pfh = fh;
2904 int pfb = fb;
2905 if (parent) {
2906 pem = parent->getFont()->getSize();
2907 pfh = parent->getFont()->getHeight();
2908 pfb = parent->getFont()->getBaseline();
2909 }
2910 if (vertical_align.type == css_val_unspecified) { // named values
2911 switch (style->vertical_align.value) {
2912 case css_va_sub:
2913 // "Lower the baseline of the box to the proper position for subscripts
2914 // of the parent's box."
2915 // Use a fraction of the height below the baseline only
2916 // 3/5 looks perfect with some fonts, 5/5 looks perfect with
2917 // some others, so use 4/5 (which is not the finest with some
2918 // fonts, but a sane middle ground)
2919 valign_dy += (pfh - pfb)*4/5;
2920 flags |= LTEXT_VALIGN_SUB;
2921 break;
2922 case css_va_super:
2923 // "Raise the baseline of the box to the proper position for superscripts
2924 // of the parent's box."
2925 // 1/4 of the font height looks alright with most fonts (we could also
2926 // use a fraction of 'baseline' only, the height above the baseline)
2927 valign_dy -= pfh / 4;
2928 flags |= LTEXT_VALIGN_SUPER;
2929 break;
2930 case css_va_middle:
2931 // "Align the vertical midpoint of the box with the baseline of the parent box
2932 // plus half the x-height of the parent."
2933 // For CSS lengths, we approximate 'ex' with 1/2 'em'. Let's do the same here.
2934 // (Firefox falls back to 0.56 x ascender for x-height:
2935 // valign_dy -= 0.56 * pfb / 2; but this looks a little too low)
2936 if (is_object || is_inline_box)
2937 valign_dy -= pem/4; // y for middle of image (lvtextfm.cpp will know from flags)
2938 else {
2939 valign_dy += fb - fh/2; // move down current middle point to baseline
2940 valign_dy -= pem/4; // move up by half of parent ex
2941 // This looks different from Firefox rendering, but actually a bit a
2942 // better "middle" to me:
2943 // valign_dy -= pem/2 - em/2;
2944 }
2945 flags |= LTEXT_VALIGN_MIDDLE;
2946 break;
2947 case css_va_text_bottom:
2948 // "Align the bottom of the box with the bottom of the parent's content area"
2949 // With valign_dy=0, they are centered on the baseline. We want
2950 // them centered on their bottom line
2951 if (is_object || is_inline_box)
2952 valign_dy += (pfh - pfb); // y for bottom of image (lvtextfm.cpp will know from flags)
2953 else
2954 valign_dy += (pfh - pfb) - (fh - fb) - f_half_leading;
2955 flags |= LTEXT_VALIGN_TEXT_BOTTOM;
2956 break;
2957 case css_va_text_top:
2958 // "Align the top of the box with the top of the parent's content area"
2959 // With valign_dy=0, they are centered on the baseline. We want
2960 // them centered on their top line
2961 if (is_object || is_inline_box)
2962 valign_dy -= pfb; // y for top of image (lvtextfm.cpp will know from flags)
2963 else
2964 valign_dy -= pfb - fb - f_half_leading;
2965 flags |= LTEXT_VALIGN_TEXT_TOP;
2966 break;
2967 case css_va_bottom:
2968 // "Align the bottom of the aligned subtree with the bottom of the line box"
2969 // This will be computed in lvtextfm.cpp when the full line has been laid out.
2970 valign_dy = 0; // dummy value
2971 flags |= LTEXT_VALIGN_BOTTOM;
2972 break;
2973 case css_va_top:
2974 // "Align the top of the aligned subtree with the top of the line box."
2975 // This will be computed in lvtextfm.cpp when the full line has been laid out.
2976 valign_dy = 0; // dummy value
2977 flags |= LTEXT_VALIGN_TOP;
2978 break;
2979 case css_va_baseline:
2980 default:
2981 break;
2982 }
2983 }
2984 else {
2985 // "<percentage> Raise (positive value) or lower (negative value) the box by this
2986 // distance (a percentage of the 'line-height' value).
2987 // <length> Raise (positive value) or lower (negative value) the box by this distance"
2988 // No mention if the base for 'em' should be the current font, or
2989 // the parent font, and if we should use ->getHeight() or ->getSize().
2990 // But using the current font size looks correct and similar when
2991 // comparing to Firefox rendering.
2992 int base_em = em; // use current font ->getSize()
2993 int base_pct = line_h;
2994 // positive values push text up, so reduce dy
2995 valign_dy -= lengthToPx(vertical_align, base_pct, base_em);
2996 }
2997 }
2998 switch ( style->text_decoration ) {
2999 case css_td_underline:
3000 case css_td_blink: // (render it underlined)
3001 flags |= LTEXT_TD_UNDERLINE;
3002 break;
3003 case css_td_overline:
3004 flags |= LTEXT_TD_OVERLINE;
3005 break;
3006 case css_td_line_through:
3007 flags |= LTEXT_TD_LINE_THROUGH;
3008 break;
3009 default:
3010 break;
3011 }
3012 switch ( style->hyphenate ) {
3013 case css_hyph_auto:
3014 flags |= LTEXT_HYPHENATE;
3015 break;
3016 case css_hyph_none:
3017 flags &= ~LTEXT_HYPHENATE;
3018 break;
3019 default:
3020 break;
3021 }
3022
3023 // Firefox has some specific behaviour with floats, which
3024 // is not obvious from the specs. Let's do as it does.
3025 // It looks like we should do the same for inline-block boxes
3026 if ( parent && (parent->isFloatingBox() || parent->isBoxingInlineBox()) ) {
3027 if ( rm == erm_final && is_object ) {
3028 // When an image is the single top final node in a float (which is
3029 // the case for individual floating images (<IMG style="float: left">),
3030 // Firefox does not enforce the strut, line-height, vertical-align and
3031 // text-indent (but it does when in <SPAN style="float: left"><IMG/></SPAN>).
3032 txform->setStrut(0, 0);
3033 line_h = 0;
3034 indent = 0;
3035 // Also, when such a floating image has a width in %, this width
3036 // has been used to set the width of the floating box. We need to
3037 // update this % width to be 100%, otherwise the image would be
3038 // again set to this % of the floating box width...
3039 // This feels a bit hacky, there might be a better place to deal with that...
3040 if (style->width.type == css_val_percent && style->width.value != 100*256) {
3041 css_style_ref_t oldstyle = enode->getStyle();
3042 css_style_ref_t newstyle(new css_style_rec_t);
3043 copystyle(oldstyle, newstyle);
3044 newstyle->width.value = 100*256; // 100%
3045 enode->setStyle(newstyle);
3046 style = enode->getStyle().get(); // update to the new style
3047 }
3048 }
3049 // Also, the floating element or inline-block inner element vertical-align drift is dropped
3050 valign_dy = 0;
3051 flags &= ~LTEXT_VALIGN_MASK; // also remove any such flag we've set
3052 flags &= ~LTEXT_STRUT_CONFINED; // remove this if it's been set above
3053 // (Looks like nothing special to do with indent or line_h)
3054 }
3055
3056 if ( style->display == css_d_list_item_legacy ) { // obsolete (used only when gDOMVersionRequested < 20180524)
3057 // put item number/marker to list
3058 lString32 marker;
3059 int marker_width = 0;
3060
3061 ListNumberingPropsRef listProps = enode->getDocument()->getNodeNumberingProps( enode->getParentNode()->getDataIndex() );
3062 if ( listProps.isNull() ) { // no previously cached info: compute and cache it
3063 // Scan all our siblings to know the widest marker width
3064 int counterValue = 0;
3065 int maxWidth = 0;
3066 ldomNode * sibling = enode->getUnboxedParent()->getUnboxedFirstChild(true);
3067 while ( sibling ) {
3068 lString32 marker;
3069 int markerWidth = 0;
3070 if ( sibling->getNodeListMarker( counterValue, marker, markerWidth ) ) {
3071 if ( markerWidth > maxWidth )
3072 maxWidth = markerWidth;
3073 }
3074 sibling = sibling->getUnboxedNextSibling(true); // skip text nodes
3075 }
3076 listProps = ListNumberingPropsRef( new ListNumberingProps(counterValue, maxWidth) );
3077 enode->getDocument()->setNodeNumberingProps( enode->getParentNode()->getDataIndex(), listProps );
3078 }
3079 int counterValue = 0;
3080 if ( enode->getNodeListMarker( counterValue, marker, marker_width ) ) {
3081 if ( !listProps.isNull() )
3082 marker_width = listProps->maxWidth;
3083 css_list_style_position_t sp = style->list_style_position;
3084 LVFontRef font = enode->getFont();
3085 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3086 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3087 int margin = 0;
3088 if ( sp==css_lsp_outside )
3089 margin = -marker_width; // will ensure negative/hanging indent-like rendering
3090 marker += "\t";
3091 txform->AddSourceLine( marker.c_str(), marker.length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy,
3092 margin, NULL );
3093 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH;
3094 }
3095 }
3096
3097 // List item marker rendering when css_d_list_item_block and list-style-position = inside:
3098 // render the marker if any, and continue rendering text on same line
3099 if ( style->display == css_d_list_item_block ) {
3100 // list_item_block rendered as final (containing only text and inline elements)
3101 // (we don't draw anything when list-style-type=none)
3102 if ( renderAsListStylePositionInside(style, is_rtl) && style->list_style_type != css_lst_none ) {
3103 int marker_width;
3104 lString32 marker = renderListItemMarker( enode, marker_width, txform, line_h, flags );
3105 if ( marker.length() ) {
3106 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH;
3107 }
3108 }
3109 }
3110 if ( rm == erm_final ) {
3111 // when list_item_block has been rendered as block (containing text and block elements)
3112 // and list-style-position=inside (or outside when text-align center or right), the
3113 // list item marker is to be propagated to the first erm_final child.
3114 // In renderBlockElement(), we saved the list item node index into the
3115 // RenderRectAccessor of the first child rendered as final.
3116 // So if we find one, we know we have to add the marker here.
3117 // (Nothing specific to do if RTL: we just add the marker to the txform content,
3118 // which is still done in logical order.)
3119 int listPropNodeIndex = fmt->getListPropNodeIndex();
3120 if ( listPropNodeIndex ) {
3121 ldomNode * list_item_block_parent = enode->getDocument()->getTinyNode( listPropNodeIndex );
3122 int marker_width;
3123 lString32 marker = renderListItemMarker( list_item_block_parent, marker_width, txform, line_h, flags );
3124 if ( marker.length() ) {
3125 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH;
3126 }
3127 }
3128 }
3129
3130 if ( is_object ) { // object element, like <IMG>
3131 #ifdef DEBUG_DUMP_ENABLED
3132 logfile << "+OBJECT ";
3133 #endif
3134 bool isBlock = style->display == css_d_block;
3135 if ( isBlock ) {
3136 // If block image, forget any current flags and start from baseflags (?)
3137 lUInt32 flags = styleToTextFmtFlags( true, enode->getStyle(), baseflags, direction );
3138 //txform->AddSourceLine(U"title", 5, 0x000000, 0xffffff, font, baseflags, interval, margin, NULL, 0, 0);
3139 LVFontRef font = enode->getFont();
3140 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3141 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3142 lString32 title;
3143 //txform->AddSourceLine( title.c_str(), title.length(), cl, bgcl, font, LTEXT_FLAG_OWNTEXT|LTEXT_FLAG_NEWLINE, line_h, 0, NULL );
3144 //baseflags
3145 title = enode->getAttributeValue(attr_suptitle);
3146 if ( !title.empty() ) {
3147 lString32Collection lines;
3148 lines.parse(title, cs32("\\n"), true);
3149 for ( int i=0; i<lines.length(); i++ )
3150 txform->AddSourceLine( lines[i].c_str(), lines[i].length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, 0, NULL );
3151 }
3152 txform->AddSourceObject(flags, line_h, valign_dy, indent, enode, lang_cfg );
3153 title = enode->getAttributeValue(attr_subtitle);
3154 if ( !title.empty() ) {
3155 lString32Collection lines;
3156 lines.parse(title, cs32("\\n"), true);
3157 for ( int i=0; i<lines.length(); i++ )
3158 txform->AddSourceLine( lines[i].c_str(), lines[i].length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, 0, NULL );
3159 }
3160 title = enode->getAttributeValue(attr_title);
3161 if ( !title.empty() ) {
3162 lString32Collection lines;
3163 lines.parse(title, cs32("\\n"), true);
3164 for ( int i=0; i<lines.length(); i++ )
3165 txform->AddSourceLine( lines[i].c_str(), lines[i].length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, 0, NULL );
3166 }
3167 } else { // inline image
3168 // We use the flags computed previously (and not baseflags) as they
3169 // carry vertical alignment
3170 txform->AddSourceObject(flags, line_h, valign_dy, indent, enode, lang_cfg );
3171 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3172 }
3173 }
3174 else if ( is_inline_box ) { // inline-block wrapper
3175 #ifdef DEBUG_DUMP_ENABLED
3176 logfile << "+INLINEBOX ";
3177 #endif
3178 bool is_embedded_block = enode->isEmbeddedBlockBoxingInlineBox();
3179 if ( is_embedded_block ) {
3180 // If embedded-block wrapper: it should not be part of the lines
3181 // made by the surrounding text/elements: we should ensure a new
3182 // line before and after it.
3183 if ( !(flags & LTEXT_FLAG_NEWLINE) ) { // (Keep existing one if not yet consumed)
3184 // The text-align of the paragraph has been inherited by
3185 // all its children, including this inlineBox wrapper.
3186 switch (style->text_align) {
3187 case css_ta_left:
3188 flags |= LTEXT_ALIGN_LEFT;
3189 break;
3190 case css_ta_right:
3191 flags |= LTEXT_ALIGN_RIGHT;
3192 break;
3193 case css_ta_center:
3194 flags |= LTEXT_ALIGN_CENTER;
3195 break;
3196 case css_ta_justify:
3197 flags |= LTEXT_ALIGN_WIDTH;
3198 break;
3199 case css_ta_start:
3200 flags |= (is_rtl ? LTEXT_ALIGN_RIGHT : LTEXT_ALIGN_LEFT);
3201 break;
3202 case css_ta_end:
3203 flags |= (is_rtl ? LTEXT_ALIGN_LEFT : LTEXT_ALIGN_RIGHT);
3204 break;
3205 case css_ta_inherit:
3206 default: // others values shouldn't happen (only accepted with text-align-last)
3207 break;
3208 }
3209 }
3210 // These might have no effect, but let's explicitely drop them.
3211 valign_dy = 0;
3212 indent = 0;
3213 // Note: a space just before or just after (because of a newline in
3214 // the HTML source) should have been removed or included in the
3215 // boxing element - so we shouldn't have any spurious empty line
3216 // in this final block (except it that space is included in some
3217 // other inline element (<span> </span>) in which case, it is
3218 // explicitely expected to generate an empty line.
3219 // We also use LTEXT_SRC_IS_INLINE_BOX (no need to waste a bit in
3220 // the lUInt32 for LTEXT_SRC_IS_EMBEDDED_BLOCK).
3221 }
3222 // We use the flags computed previously (and not baseflags) as they
3223 // carry vertical alignment
3224 txform->AddSourceObject(flags|LTEXT_SRC_IS_INLINE_BOX, line_h, valign_dy, indent, enode, lang_cfg );
3225 if ( is_embedded_block ) {
3226 // Let flags unchanged, with their newline/alignment flag as if it
3227 // hadn't been consumed, so it is reported back into baseflags below
3228 // so that the next sibling (or upper followup inline node) starts
3229 // on a new line.
3230 }
3231 else {
3232 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3233 }
3234 }
3235 else { // non-IMG element: render children (elements or text nodes)
3236 int cnt = enode->getChildCount();
3237 #ifdef DEBUG_DUMP_ENABLED
3238 logfile << "+BLOCK [" << cnt << "]";
3239 #endif
3240 // Usual elements
3241
3242 // Some elements add some generated content
3243 lUInt16 nodeElementId = enode->getNodeId();
3244 // Don't handle dir= for the erm_final (<p dir="auto"), as it would "isolate"
3245 // the whole content from the bidi algorithm and we would get a default paragraph
3246 // direction of LTR. It is handled directly in lvtextfm.cpp.
3247 bool hasDirAttribute = rm != erm_final && enode->hasAttribute( attr_dir );
3248 bool addGeneratedContent = hasDirAttribute ||
3249 nodeElementId == el_bdi ||
3250 nodeElementId == el_bdo ||
3251 nodeElementId == el_pseudoElem;
3252 bool closeWithPDI = false;
3253 bool closeWithPDF = false;
3254 bool closeWithPDFPDI = false;
3255 if ( addGeneratedContent ) {
3256 // Note: we need to explicitely clear newline flag after
3257 // any txform->AddSourceLine(). If we delay that and add another
3258 // char before, this other char would generate a new line.
3259 LVFontRef font = enode->getFont();
3260 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3261 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3262
3263 // The following is needed for fribidi to do the right thing when the content creator
3264 // has provided hints to explicite ambiguous cases.
3265 // <bdi> and <bdo> are HTML5 tags allowing to inform or override the bidi algorithm.
3266 // When meeting them, we add the equivalent unicode opening and closing chars so
3267 // that fribidi (working on text only) can ensure what's specified with HTML tags.
3268 // See http://unicode.org/reports/tr9/#Markup_And_Formatting
3269 lString32 dir = enode->getAttributeValue( attr_dir );
3270 dir = dir.lowercase(); // (no need for trim(), it's done by the XMLParser)
3271 if ( nodeElementId == el_bdo ) {
3272 // <bdo> (bidirectional override): prevents the bidirectional algorithm from
3273 // rearranging the sequence of characters it encloses
3274 // dir=ltr => LRO U+202D LEFT-TO-RIGHT OVERRIDE
3275 // dir=rtl => RLO U+202E RIGHT-TO-LEFT OVERRIDE
3276 // leaving => PDF U+202C POP DIRECTIONAL FORMATTING
3277 // The link above suggest using these combinations:
3278 // dir=ltr => FSI LRO
3279 // dir=rtl => FSI RLO
3280 // leaving => PDF PDI
3281 // but it then doesn't have the intended effect (fribidi bug or limitation?)
3282 if ( dir.compare("rtl") == 0 ) {
3283 // txform->AddSourceLine( U"\x2068\x202E", 1, cl, bgcl, font, lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3284 // closeWithPDFPDI = true;
3285 txform->AddSourceLine( U"\x202E", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3286 closeWithPDF = true;
3287 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3288 }
3289 else if ( dir.compare("ltr") == 0 ) {
3290 // txform->AddSourceLine( U"\x2068\x202D", 1, cl, bgcl, font, lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3291 // closeWithPDFPDI = true;
3292 txform->AddSourceLine( U"\x202D", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3293 closeWithPDF = true;
3294 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3295 }
3296 }
3297 else if ( hasDirAttribute || nodeElementId == el_bdi ) {
3298 // <bdi> (bidirectional isolate): isolates its content from the surrounding text,
3299 // and to be used also for any inline elements with "dir=":
3300 // dir=ltr => LRI U+2066 LEFT-TO-RIGHT ISOLATE
3301 // dir=rtl => RLI U+2067 RIGHT-TO-LEFT ISOLATE
3302 // dir=auto => FSI U+2068 FIRST STRONG ISOLATE
3303 // leaving => PDI U+2069 POP DIRECTIONAL ISOLATE
3304 if ( dir.compare("rtl") == 0 ) {
3305 txform->AddSourceLine( U"\x2067", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3306 closeWithPDI = true;
3307 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3308 }
3309 else if ( dir.compare("ltr") == 0 ) {
3310 txform->AddSourceLine( U"\x2066", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3311 closeWithPDI = true;
3312 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3313 }
3314 else if ( nodeElementId == el_bdi || dir.compare("auto") == 0 ) {
3315 txform->AddSourceLine( U"\x2068", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3316 closeWithPDI = true;
3317 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3318 }
3319 // Pre HTML5, we would have used for any inline tag with a dir= attribute:
3320 // dir=ltr => LRE U+202A LEFT-TO-RIGHT EMBEDDING
3321 // dir=rtl => RLE U+202B RIGHT-TO-LEFT EMBEDDING
3322 // leaving => PDF U+202C POP DIRECTIONAL FORMATTING
3323 }
3324 // Note: in lvtextfm, we have to explicitely ignore these (added by us,
3325 // or already present in the HTML), in measurement and drawing, as
3326 // FreeType could draw some real glyphes for these, when the font
3327 // provide a glyph (ie: "[FSI]"). No issue when HarfBuzz is used.
3328 //
3329 // Note: if we wanted to support <ruby> tags, we could use the same kind
3330 // of trick. Unicode provides U+FFF9 to U+FFFA to wrap ruby content.
3331 // HarfBuzz does not support these (because multiple font sizes would
3332 // be involved for drawing ruby), but lvtextfm could deal with these
3333 // itself (by ignoring them in measurement, going back the previous
3334 // advance, increasing the line height, drawing above...)
3335
3336 // BiDi stuff had to be outputed first, before any pseudo element
3337 // (if <q dir="rtl">...</q>, the added quote (first child pseudo element)
3338 // should be inside the RTL bidi isolation.
3339 if ( nodeElementId == el_pseudoElem ) {
3340 lString32 content = get_applied_content_property(enode);
3341 if ( !content.empty() ) {
3342 int em = font->getSize();
3343 int letter_spacing = lengthToPx(style->letter_spacing, em, em);
3344 txform->AddSourceLine( content.c_str(), content.length(), cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent, NULL, 0, letter_spacing);
3345 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3346 }
3347 }
3348 }
3349
3350 // is_link_start is given to inner elements (to flag the first
3351 // text node part of a link), and will be reset to false by
3352 // the first non-space-only text node
3353 bool * is_link_start_p = is_link_start; // copy of orignal (possibly NULL) pointer
3354 bool tmp_is_link_start = true; // new bool, for new pointer if we're a <A>
3355 if ( nodeElementId == el_a ) {
3356 is_link_start_p = &tmp_is_link_start; // use new pointer
3357 }
3358 for (int i=0; i<cnt; i++)
3359 {
3360 ldomNode * child = enode->getChildNode( i );
3361 renderFinalBlock( child, txform, fmt, flags, indent, line_h, lang_cfg, valign_dy, is_link_start_p );
3362 }
3363
3364 if ( addGeneratedContent ) {
3365 LVFontRef font = enode->getFont();
3366 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3367 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3368 // See comment above: these are the closing counterpart
3369 if ( closeWithPDI ) {
3370 txform->AddSourceLine( U"\x2069", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3371 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3372 }
3373 else if ( closeWithPDFPDI ) {
3374 txform->AddSourceLine( U"\x202C\x2069", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3375 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3376 }
3377 else if ( closeWithPDF ) {
3378 txform->AddSourceLine( U"\x202C", 1, cl, bgcl, font.get(), lang_cfg, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, indent);
3379 flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3380 }
3381 }
3382
3383 if ( isRunIn ) {
3384 // Append space to run-in object: both the run-in text node and
3385 // the following paragraph first text node might not end or start
3386 // with a space. But they might also both do, and we want all spaces
3387 // to collapse into one - so, we don't set LTEXT_FLAG_PREFORMATTED,
3388 // and we don't use UNICODE_NO_BREAK_SPACE.
3389 LVFontRef font = enode->getFont();
3390 css_style_ref_t style = enode->getStyle();
3391 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3392 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3393 txform->AddSourceLine( U" ", 1, cl, bgcl, font.get(), lang_cfg, LTEXT_LOCKED_SPACING|LTEXT_FLAG_OWNTEXT, line_h, valign_dy);
3394 /*
3395 // We used to specify two UNICODE_NO_BREAK_SPACE (that would not collapse)
3396 // mostly so we were able to detect them in lvtextfm.cpp and avoid this
3397 // spacing to change width with text justification.
3398 lChar32 delimiter[] = {UNICODE_NO_BREAK_SPACE, UNICODE_NO_BREAK_SPACE}; //160
3399 txform->AddSourceLine( delimiter, sizeof(delimiter)/sizeof(lChar32), cl, bgcl, font.get(), lang_cfg,
3400 LTEXT_FLAG_PREFORMATTED | LTEXT_FLAG_OWNTEXT, line_h, valign_dy, 0, NULL );
3401 // Users who would like more spacing can use:
3402 // body[name="notes"] section title:after,
3403 // body[name="comments"] section title:after {
3404 // content: '\A0'
3405 // }
3406 // But the text nodes spaces will then not collapse, and constant spacing
3407 // won't be ensured (spacing may vary from one document to another).
3408 */
3409 }
3410 }
3411
3412 #ifdef DEBUG_DUMP_ENABLED
3413 for (int i=0; i<enode->getNodeLevel(); i++)
3414 logfile << " . ";
3415 lvRect rect;
3416 enode->getAbsRect( rect );
3417 logfile << "<" << enode->getNodeName() << "> flags( "
3418 << baseflags << "-> " << flags << ") rect( "
3419 << rect.left << rect.top << rect.right << rect.bottom << ")\n";
3420 #endif
3421
3422 // Children may have consumed the newline flag, or may have added one
3423 // (if the last one of them is a <BR>, it will not have been consumed
3424 // eg. with <P><SMALL>Some small text<BR/></SMALL> and normal text</P>)
3425 // So, forward the newline state from flags to baseflags:
3426 if ( flags & LTEXT_FLAG_NEWLINE ) {
3427 baseflags |= flags & LTEXT_FLAG_NEWLINE;
3428 // Also forward any CLEAR flag not consumed
3429 baseflags |= flags & LTEXT_SRC_IS_CLEAR_BOTH;
3430 }
3431 else { // newline consumed
3432 baseflags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3433 }
3434 if ( enode->getNodeId()==el_br ) {
3435 if (baseflags & LTEXT_FLAG_NEWLINE) {
3436 // We meet a <BR/>, but no text node were met before (or it
3437 // would have cleared the newline flag).
3438 // Output a single space so that a blank line can be made,
3439 // as wanted by a <BR/>.
3440 // (This makes consecutive and stuck <br><br><br> work)
3441 LVFontRef font = enode->getFont();
3442 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3443 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3444 txform->AddSourceLine( U" ", 1, cl, bgcl, font.get(), lang_cfg,
3445 baseflags | LTEXT_FLAG_PREFORMATTED | LTEXT_FLAG_OWNTEXT,
3446 line_h, valign_dy);
3447 // baseflags &= ~LTEXT_FLAG_NEWLINE; // clear newline flag
3448 // No need to clear the flag, as we set it just below
3449 // (any LTEXT_ALIGN_* set implies LTEXT_FLAG_NEWLINE)
3450 }
3451 // Re-set the newline and aligment flag for what's coming
3452 // after this <BR/>
3453 //baseflags |= LTEXT_ALIGN_LEFT;
3454 switch (style->text_align) {
3455 case css_ta_left:
3456 baseflags |= LTEXT_ALIGN_LEFT;
3457 break;
3458 case css_ta_right:
3459 baseflags |= LTEXT_ALIGN_RIGHT;
3460 break;
3461 case css_ta_center:
3462 baseflags |= LTEXT_ALIGN_CENTER;
3463 break;
3464 case css_ta_justify:
3465 baseflags |= LTEXT_ALIGN_WIDTH;
3466 break;
3467 case css_ta_start:
3468 baseflags |= (is_rtl ? LTEXT_ALIGN_RIGHT : LTEXT_ALIGN_LEFT);
3469 break;
3470 case css_ta_end:
3471 baseflags |= (is_rtl ? LTEXT_ALIGN_LEFT : LTEXT_ALIGN_RIGHT);
3472 break;
3473 case css_ta_inherit:
3474 default: // others values shouldn't happen (only accepted with text-align-last)
3475 break;
3476 }
3477 // Among inline nodes, only <BR> can carry a "clear: left/right/both".
3478 // (No need to check for BLOCK_RENDERING_FLOAT_FLOATBOXES, this
3479 // should have no effect when there is not a single float in the way)
3480 baseflags &= ~LTEXT_SRC_IS_CLEAR_BOTH; // clear previous one
3481 switch (style->clear) {
3482 case css_c_left:
3483 baseflags |= LTEXT_SRC_IS_CLEAR_LEFT;
3484 break;
3485 case css_c_right:
3486 baseflags |= LTEXT_SRC_IS_CLEAR_RIGHT;
3487 break;
3488 case css_c_both:
3489 baseflags |= LTEXT_SRC_IS_CLEAR_BOTH;
3490 break;
3491 default:
3492 break;
3493 }
3494 }
3495 if ( rm == erm_final && (baseflags & LTEXT_SRC_IS_CLEAR_BOTH) ) {
3496 // We're leaving the top final node with a clear: not consumed
3497 // (set by a last or single <br clear=>), with no follow-up
3498 // txform->AddSourceLine() that would have carried it.
3499 // Add an empty source: this should be managed specifically
3500 // by lvtextfm.cpp splitParagraphs() to not add this empty
3501 // string to text, and just call floatClearText().
3502 LVFontRef font = enode->getFont();
3503 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3504 lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value;
3505 txform->AddSourceLine( U" ", 1, cl, bgcl, font.get(), lang_cfg,
3506 baseflags | LTEXT_SRC_IS_CLEAR_LAST | LTEXT_FLAG_PREFORMATTED | LTEXT_FLAG_OWNTEXT,
3507 line_h, valign_dy);
3508 }
3509 }
3510 else if ( enode->isText() ) {
3511 // text nodes
3512 lString32 txt = enode->getText();
3513 if ( !txt.empty() ) {
3514 #ifdef DEBUG_DUMP_ENABLED
3515 for (int i=0; i<enode->getNodeLevel(); i++)
3516 logfile << " . ";
3517 logfile << "#text" << " flags( "
3518 << baseflags << ")\n";
3519 #endif
3520
3521 ldomNode * parent = enode->getParentNode();
3522 lUInt32 tflags = LTEXT_FLAG_OWNTEXT;
3523 // if ( parent->getNodeId() == el_a ) // "123" in <a href=><sup>123</sup></a> would not be flagged
3524 if (is_link_start && *is_link_start) { // was propagated from some outer <A>
3525 tflags |= LTEXT_IS_LINK; // used to gather in-page footnotes
3526 lString32 tmp = lString32(txt);
3527 if (!tmp.trim().empty()) // non empty text, will make out a word
3528 *is_link_start = false;
3529 // reset to false, so next text nodes in that link are not
3530 // flagged, and don't make out duplicate in-page footnotes
3531 }
3532 LVFontRef font = parent->getFont();
3533 css_style_ref_t style = parent->getStyle();
3534 lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value;
3535 lUInt32 bgcl = 0xFFFFFFFF;
3536 if ( style->background_color.type == css_val_color && (lUInt32)style->background_color.value != 0xFFFFFFFF ) {
3537 bgcl = style->background_color.value;
3538 // Avoid painting same background color as upper node, as it may cover any background image
3539 ldomNode * gparent = parent->getParentNode();
3540 if( gparent && !gparent->isNull() ) {
3541 css_length_t gparent_bgcolor = gparent->getStyle()->background_color;
3542 if ( gparent_bgcolor.type == css_val_color && (lUInt32)gparent_bgcolor.value == bgcl ) {
3543 bgcl=0xFFFFFFFF;
3544 }
3545 }
3546 }
3547
3548 switch (style->text_transform) {
3549 case css_tt_uppercase:
3550 txt.uppercase();
3551 break;
3552 case css_tt_lowercase:
3553 txt.lowercase();
3554 break;
3555 case css_tt_capitalize:
3556 txt.capitalize();
3557 break;
3558 case css_tt_full_width:
3559 // txt.fullWidthChars(); // disabled for now (may change CJK rendering)
3560 break;
3561 case css_tt_none:
3562 case css_tt_inherit:
3563 break;
3564 }
3565
3566 int letter_spacing;
3567 // % is not supported for letter_spacing by Firefox, but crengine
3568 // did support it, by relating it to font size, so let's use em
3569 // in place of width
3570 // lengthToPx() will correctly return 0 with css_generic_normal
3571 int em = font->getSize();
3572 letter_spacing = lengthToPx(style->letter_spacing, em, em);
3573 /*
3574 if ( baseflags & LTEXT_FLAG_PREFORMATTED ) {
3575 int flags = baseflags | tflags;
3576 lString32Collection lines;
3577 SplitLines( txt, lines );
3578 for ( int k=0; k<lines.length(); k++ ) {
3579 lString32 str = lines[k];
3580 txform->AddSourceLine( str.c_str(), str.length(), cl, bgcl,
3581 font, flags, line_h, 0, node, 0, letter_spacing );
3582 flags &= ~LTEXT_FLAG_NEWLINE;
3583 flags |= LTEXT_ALIGN_LEFT;
3584 }
3585 } else {
3586 }
3587 */
3588 if ( legacy_rendering ) {
3589 // Removal of leading spaces is now managed directly by lvtextfm
3590 // but in legacy render mode we don't add lines with only spaces.
3591 //int offs = 0;
3592 if ( (txform->GetSrcCount()==0 || (tflags & LTEXT_IS_LINK)) && style->white_space!=css_ws_pre ) {
3593 // clear leading spaces for first text of paragraph
3594 int i=0;
3595 for ( ;txt.length()>i && (txt[i]==' ' || txt[i]=='\t'); i++ )
3596 ;
3597 if ( i>0 ) {
3598 txt.erase(0, i);
3599 //offs = i;
3600 }
3601 }
3602 // legacy new line processing: set indentation for **each** new line
3603 tflags |= LTEXT_LEGACY_RENDERING;
3604 }
3605 if ( txt.length()>0 ) {
3606 txform->AddSourceLine( txt.c_str(), txt.length(), cl, bgcl, font.get(), lang_cfg, baseflags | tflags,
3607 line_h, valign_dy, indent, enode, 0, letter_spacing );
3608 baseflags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag
3609 // To show the lang tag for the lang used for this text node AFTER it:
3610 // lString32 lang_tag_txt = U"[" + (lang_cfg ? lang_cfg->getLangTag() : lString32("??")) + U"]";
3611 // txform->AddSourceLine( lang_tag_txt.c_str(), lang_tag_txt.length(), cl, bgcl, font,
3612 // lang_cfg, baseflags|tflags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy, 0, NULL );
3613 }
3614 }
3615 }
3616 else {
3617 crFatalError(142, "Unexpected node type");
3618 }
3619 }
3620
CssPageBreak2Flags(css_page_break_t prop)3621 int CssPageBreak2Flags( css_page_break_t prop )
3622 {
3623 switch (prop)
3624 {
3625 case css_pb_auto:
3626 return RN_SPLIT_AUTO;
3627 case css_pb_avoid:
3628 return RN_SPLIT_AVOID;
3629 case css_pb_always:
3630 case css_pb_left:
3631 case css_pb_right:
3632 case css_pb_page:
3633 case css_pb_recto:
3634 case css_pb_verso:
3635 return RN_SPLIT_ALWAYS;
3636 default:
3637 return RN_SPLIT_AUTO;
3638 }
3639 }
3640
3641 // Only used by renderBlockElementLegacy()
isFirstBlockChild(ldomNode * parent,ldomNode * child)3642 bool isFirstBlockChild( ldomNode * parent, ldomNode * child ) {
3643 int count = parent->getChildCount();
3644 for ( int i=0; i<count; i++ ) {
3645 ldomNode * el = parent->getChildNode(i);
3646 if ( el==child )
3647 return true;
3648 if ( el->isElement() ) {
3649 lvdom_element_render_method rm = el->getRendMethod();
3650 if ( rm==erm_final || rm==erm_block ) {
3651 RenderRectAccessor acc(el);
3652 if ( acc.getHeight()>5 )
3653 return false;
3654 }
3655 }
3656 }
3657 return true;
3658 }
3659
copystyle(css_style_ref_t source,css_style_ref_t dest)3660 void copystyle( css_style_ref_t source, css_style_ref_t dest )
3661 {
3662 dest->display = source->display ;
3663 dest->white_space = source->white_space ;
3664 dest->text_align = source->text_align ;
3665 dest->text_align_last = source->text_align_last ;
3666 dest->text_decoration = source->text_decoration ;
3667 dest->text_transform = source->text_transform ;
3668 dest->vertical_align = source->vertical_align ;
3669 dest->font_family = source->font_family;
3670 dest->font_name = source->font_name ;
3671 dest->font_size.type = source->font_size.type ;
3672 dest->font_size.value = source->font_size.value ;
3673 dest->font_style = source->font_style ;
3674 dest->font_weight = source->font_weight ;
3675 dest->font_features.type = source->font_features.type ;
3676 dest->font_features.value = source->font_features.value ;
3677 dest->text_indent = source->text_indent ;
3678 dest->line_height = source->line_height ;
3679 dest->width = source->width ;
3680 dest->height = source->height ;
3681 dest->margin[0] = source->margin[0] ;
3682 dest->margin[1] = source->margin[1] ;
3683 dest->margin[2] = source->margin[2] ;
3684 dest->margin[3] = source->margin[3] ;
3685 dest->padding[0] = source->padding[0] ;
3686 dest->padding[1] = source->padding[1] ;
3687 dest->padding[2] = source->padding[2] ;
3688 dest->padding[3] = source->padding[3] ;
3689 dest->color = source->color ;
3690 dest->background_color = source->background_color ;
3691 dest->letter_spacing = source->letter_spacing ;
3692 dest->page_break_before = source->page_break_before ;
3693 dest->page_break_after = source->page_break_after ;
3694 dest->page_break_inside = source->page_break_inside ;
3695 dest->hyphenate = source->hyphenate ;
3696 dest->list_style_type = source->list_style_type ;
3697 dest->list_style_position = source->list_style_position ;
3698 dest->border_style_top=source->border_style_top;
3699 dest->border_style_bottom=source->border_style_bottom;
3700 dest->border_style_right=source->border_style_right;
3701 dest->border_style_left=source->border_style_left;
3702 dest->border_width[0]=source->border_width[0];
3703 dest->border_width[1]=source->border_width[1];
3704 dest->border_width[2]=source->border_width[2];
3705 dest->border_width[3]=source->border_width[3];
3706 dest->border_color[0]=source->border_color[0];
3707 dest->border_color[1]=source->border_color[1];
3708 dest->border_color[2]=source->border_color[2];
3709 dest->border_color[3]=source->border_color[3];
3710 dest->background_image=source->background_image;
3711 dest->background_repeat=source->background_repeat;
3712 dest->background_position=source->background_position;
3713 dest->background_size[0]=source->background_size[0];
3714 dest->background_size[1]=source->background_size[1];
3715 dest->border_collapse=source->border_collapse;
3716 dest->border_spacing[0]=source->border_spacing[0];
3717 dest->border_spacing[1]=source->border_spacing[1];
3718 dest->orphans = source->orphans;
3719 dest->widows = source->widows;
3720 dest->float_ = source->float_;
3721 dest->clear = source->clear;
3722 dest->direction = source->direction;
3723 dest->content = source->content ;
3724 dest->cr_hint.type = source->cr_hint.type ;
3725 dest->cr_hint.value = source->cr_hint.value ;
3726 }
3727
3728 // Only used by renderBlockElementLegacy()
getPageBreakBefore(ldomNode * el)3729 css_page_break_t getPageBreakBefore( ldomNode * el ) {
3730 if ( el->isText() )
3731 el = el->getParentNode();
3732 css_page_break_t before = css_pb_auto;
3733 while (el) {
3734 css_style_ref_t style = el->getStyle();
3735 if ( style.isNull() )
3736 return before;
3737 before = style->page_break_before;
3738 if ( before!=css_pb_auto )
3739 {
3740 if(!style.isNull())
3741 {
3742 // we should not modify styles directly, as the change in style cache will affect other
3743 // node with same style, and corrupt style cache Hash, invalidating cache reuse
3744 css_style_ref_t newstyle( new css_style_rec_t );
3745 copystyle(style, newstyle);
3746 newstyle->page_break_before=css_pb_auto;
3747 newstyle->page_break_inside=style->page_break_inside;
3748 newstyle->page_break_after=style->page_break_after;
3749 // we should no more modify a style after it has been applied to a node with setStyle()
3750 el->setStyle(newstyle);
3751 }
3752 return before;
3753 }
3754 ldomNode * parent = el->getParentNode();
3755 if ( !parent )
3756 return before;
3757 if ( !isFirstBlockChild(parent, el) )
3758 return before;
3759 el = parent;
3760 }
3761 return before;
3762 }
3763
3764 // Only used by renderBlockElementLegacy()
getPageBreakAfter(ldomNode * el)3765 css_page_break_t getPageBreakAfter( ldomNode * el ) {
3766 if ( el->isText() )
3767 el = el->getParentNode();
3768 css_page_break_t after = css_pb_auto;
3769 bool lastChild = true;
3770 while (el) {
3771 css_style_ref_t style = el->getStyle();
3772 if ( style.isNull() )
3773 return after;
3774 if ( lastChild && after==css_pb_auto )
3775 after = style->page_break_after;
3776 if ( !lastChild || after!=css_pb_auto )
3777 return after;
3778 ldomNode * parent = el->getParentNode();
3779 if ( !parent )
3780 return after;
3781 lastChild = ( lastChild && parent->getLastChild()==el );
3782 el = parent;
3783 }
3784 return after;
3785 }
3786
3787 // Only used by renderBlockElementLegacy()
getPageBreakInside(ldomNode * el)3788 css_page_break_t getPageBreakInside( ldomNode * el ) {
3789 if ( el->isText() )
3790 el = el->getParentNode();
3791 css_page_break_t inside = css_pb_auto;
3792 while (el) {
3793 css_style_ref_t style = el->getStyle();
3794 if ( style.isNull() )
3795 return inside;
3796 if ( inside==css_pb_auto )
3797 inside = style->page_break_inside;
3798 if ( inside!=css_pb_auto )
3799 return inside;
3800 ldomNode * parent = el->getParentNode();
3801 if ( !parent )
3802 return inside;
3803 el = parent;
3804 }
3805 return inside;
3806 }
3807
3808 // Only used by renderBlockElementLegacy()
getPageBreakStyle(ldomNode * el,css_page_break_t & before,css_page_break_t & inside,css_page_break_t & after)3809 void getPageBreakStyle( ldomNode * el, css_page_break_t &before, css_page_break_t &inside, css_page_break_t &after ) {
3810 bool firstChild = true;
3811 bool lastChild = true;
3812 before = inside = after = css_pb_auto;
3813 while (el) {
3814 css_style_ref_t style = el->getStyle();
3815 if ( style.isNull() )
3816 return;
3817 if ( firstChild && before==css_pb_auto ) {
3818 before = style->page_break_before;
3819 }
3820 if ( lastChild && after==css_pb_auto ) {
3821 after = style->page_break_after;
3822 }
3823 if ( inside==css_pb_auto ) {
3824 inside = style->page_break_inside;
3825 }
3826 if ( (!firstChild || before!=css_pb_auto) && (!lastChild || after!=css_pb_auto)
3827 && inside!=css_pb_auto)
3828 return;
3829 ldomNode * parent = el->getParentNode();
3830 if ( !parent )
3831 return;
3832 firstChild = ( firstChild && parent->getFirstChild()==el );
3833 lastChild = ( lastChild && parent->getLastChild()==el );
3834 el = parent;
3835 }
3836 }
3837
3838 // Default border width in screen px when border requested but no width specified
3839 #define DEFAULT_BORDER_WIDTH 2
3840
3841 //measure border width, 0 for top,1 for right,2 for bottom,3 for left
measureBorder(ldomNode * enode,int border)3842 int measureBorder(ldomNode *enode,int border) {
3843 int em = enode->getFont()->getSize();
3844 // No need for a width, as border does not support units in % according
3845 // to CSS specs.
3846 int width = 0;
3847 // (Note: another reason for disabling borders in % (that we did support)
3848 // is that, at the various places where measureBorder() is called,
3849 // fmt.setWidth() has not yet been called and fmt.getWidth() would
3850 // return 0. Later, at drawing time, fmt.getWidth() will return the real
3851 // width, which could cause rendering of borders over child elements,
3852 // as these were positioned with a border=0.)
3853 css_style_ref_t style = enode->getStyle();
3854 if (border==0){
3855 bool hastopBorder = (style->border_style_top >= css_border_solid &&
3856 style->border_style_top <= css_border_outset);
3857 if (!hastopBorder) return 0;
3858 css_length_t bw = style->border_width[0];
3859 if (bw.value == 0 && bw.type > css_val_unspecified) return 0; // explicit value of 0: no border
3860 int topBorderwidth = lengthToPx(bw, width, em);
3861 topBorderwidth = topBorderwidth != 0 ? topBorderwidth : DEFAULT_BORDER_WIDTH;
3862 return topBorderwidth;}
3863 else if (border==1){
3864 bool hasrightBorder = (style->border_style_right >= css_border_solid &&
3865 style->border_style_right <= css_border_outset);
3866 if (!hasrightBorder) return 0;
3867 css_length_t bw = style->border_width[1];
3868 if (bw.value == 0 && bw.type > css_val_unspecified) return 0;
3869 int rightBorderwidth = lengthToPx(bw, width, em);
3870 rightBorderwidth = rightBorderwidth != 0 ? rightBorderwidth : DEFAULT_BORDER_WIDTH;
3871 return rightBorderwidth;}
3872 else if (border ==2){
3873 bool hasbottomBorder = (style->border_style_bottom >= css_border_solid &&
3874 style->border_style_bottom <= css_border_outset);
3875 if (!hasbottomBorder) return 0;
3876 css_length_t bw = style->border_width[2];
3877 if (bw.value == 0 && bw.type > css_val_unspecified) return 0;
3878 int bottomBorderwidth = lengthToPx(bw, width, em);
3879 bottomBorderwidth = bottomBorderwidth != 0 ? bottomBorderwidth : DEFAULT_BORDER_WIDTH;
3880 return bottomBorderwidth;}
3881 else if (border==3){
3882 bool hasleftBorder = (style->border_style_left >= css_border_solid &&
3883 style->border_style_left <= css_border_outset);
3884 if (!hasleftBorder) return 0;
3885 css_length_t bw = style->border_width[3];
3886 if (bw.value == 0 && bw.type > css_val_unspecified) return 0;
3887 int leftBorderwidth = lengthToPx(bw, width, em);
3888 leftBorderwidth = leftBorderwidth != 0 ? leftBorderwidth : DEFAULT_BORDER_WIDTH;
3889 return leftBorderwidth;}
3890 else
3891 return 0;
3892 }
3893
3894 // Only used by renderBlockElementLegacy()
3895 //calculate total margin+padding before node,if >0 don't do compulsory page split
pagebreakhelper(ldomNode * enode,int width)3896 int pagebreakhelper(ldomNode *enode,int width)
3897 {
3898 int flag=css_pb_auto;
3899 int em = enode->getFont()->getSize();
3900 int margin_top = lengthToPx( enode->getStyle()->margin[2], width, em ) + DEBUG_TREE_DRAW;
3901 int padding_top = lengthToPx( enode->getStyle()->padding[2], width, em ) + DEBUG_TREE_DRAW+measureBorder(enode,0);
3902 flag=CssPageBreak2Flags(getPageBreakBefore(enode))<<RN_SPLIT_BEFORE;
3903 if (flag==RN_SPLIT_BEFORE_ALWAYS){
3904 ldomNode *node=enode;
3905 int top=0;
3906 while (!node->isNull()) {
3907 top+=lengthToPx( node->getStyle()->margin[2], width, em ) +
3908 lengthToPx( node->getStyle()->padding[2], width, em ) +
3909 measureBorder(node,0);
3910 ldomNode * parent = node->getParentNode();
3911 if ( !parent ) break;
3912 if ( !isFirstBlockChild(parent, node) ) break;
3913 node = parent;
3914 }
3915 top-=margin_top+padding_top;
3916 if (top>0) flag=RN_SPLIT_AUTO;
3917 if ((getPageBreakBefore(enode)==css_pb_always)) flag=RN_SPLIT_ALWAYS;
3918 }
3919 return flag;
3920 }
3921
3922 //=======================================================================
3923 // Render block element
3924 //=======================================================================
3925 // renderBlockElement() aimed at positioning the node provided: setting
3926 // its width and height, and its (x,y) in the coordinates of its parent
3927 // element's border box (so, including parent's paddings and borders
3928 // top/left, but not its margins).
3929 // The provided x and y must include the parent's padding and border, and
3930 // the relative y this new node happen to be in this parent container.
3931 // renderBlockElement() will then add the node's own margins top/left to
3932 // them, to set the (x,y) of this node's border box as its position in
3933 // its parent coordinates.
3934 // So, it just shift coordinates and adjust widths of imbricated block nodes,
3935 // until it meets a "final" node (a node with text or image content), which
3936 // will then have been sized and positioned.
3937 // After the initial rendering, we mostly only care about these positioned
3938 // final nodes (for text rendering, text selection, text search, links...).
3939 // We still walk the block nodes when we need absolute coordinates, computed
3940 // from all the relative shifts of the containing block nodes boxes up to the
3941 // root node. Also when drawing, we draw background and borders of these
3942 // block nodes, nothing much else with them, until we reach a final node
3943 // and we can draw its text and images.
3944
3945 // Prototype of the entry point functions for rendering the root node, a table cell or a float, as defined in lvrend.h:
3946 // int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction=REND_DIRECTION_UNSET );
3947 // int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction, int rend_flags );
3948
3949 // Prototypes of the 2 alternative block rendering recursive functions
3950 int renderBlockElementLegacy(LVRendPageContext & context, ldomNode * enode, int x, int y, int width , int usable_right_overflow);
3951 void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int width, lUInt32 flags );
3952
3953 // Legacy/original CRE block rendering
renderBlockElementLegacy(LVRendPageContext & context,ldomNode * enode,int x,int y,int width,int usable_right_overflow)3954 int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int usable_right_overflow )
3955 {
3956 if (!enode)
3957 return 0;
3958 if ( enode->isElement() )
3959 {
3960 css_style_ref_t style = enode->getStyle();
3961 bool isFootNoteBody = false;
3962 lString32 footnoteId;
3963 // Allow displaying footnote content at the bottom of all pages that contain a link
3964 // to it, when -cr-hint: footnote-inpage is set on the footnote block container.
3965 if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) &&
3966 enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) {
3967 footnoteId = enode->getFirstInnerAttributeValue(attr_id);
3968 if ( !footnoteId.empty() )
3969 isFootNoteBody = true;
3970 // Notes:
3971 // It fails when that block element has itself an id, but links
3972 // do target an other inline sub element id (getFirstInnerAttributeValue()
3973 // would get the block element id, and there would be no existing footnote
3974 // for the link target id).
3975 // Not tested how it would behave with nested "-cr-hint: footnote-inpage"
3976 }
3977 // For fb2 documents. Description of the <body> element from FictionBook2.2.xsd:
3978 // Main content of the book, multiple bodies are used for additional
3979 // information, like footnotes, that do not appear in the main book
3980 // flow. The first body is presented to the reader by default, and
3981 // content in the other bodies should be accessible by hyperlinks. Name
3982 // attribute should describe the meaning of this body, this is optional
3983 // for the main body.
3984 /* Don't do that anymore in this hardcoded / not disable'able way: one can
3985 * enable in-page footnotes in fb2.css or a style tweak by just using:
3986 * body[name="notes"] section { -cr-hint: footnote-inpage; }
3987 * body[name="comments"] section { -cr-hint: footnote-inpage; }
3988 * which will be hanbled by previous check.
3989 *
3990 if ( enode->getNodeId()==el_section && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) {
3991 ldomNode * body = enode->getParentNode();
3992 while ( body != NULL && body->getNodeId()!=el_body )
3993 body = body->getParentNode();
3994 if ( body ) {
3995 if (body->getAttributeValue(attr_name) == "notes" || body->getAttributeValue(attr_name) == "comments")
3996 footnoteId = enode->getAttributeValue(attr_id);
3997 if ( !footnoteId.empty() )
3998 isFootNoteBody = true;
3999 }
4000 }
4001 */
4002 // if ( isFootNoteBody )
4003 // CRLog::trace("renderBlockElement() : Footnote body detected! %s", LCSTR(ldomXPointer(enode,0).toString()) );
4004 //if (!fmt)
4005 // crFatalError();
4006 // if ( enode->getNodeId() == el_empty_line )
4007 // x = x;
4008
4009 int em = enode->getFont()->getSize();
4010 int margin_left = lengthToPx( style->margin[0], width, em ) + DEBUG_TREE_DRAW;
4011 int margin_right = lengthToPx( style->margin[1], width, em ) + DEBUG_TREE_DRAW;
4012 int margin_top = lengthToPx( style->margin[2], width, em ) + DEBUG_TREE_DRAW;
4013 int margin_bottom = lengthToPx( style->margin[3], width, em ) + DEBUG_TREE_DRAW;
4014 int border_top = measureBorder(enode,0);
4015 int border_bottom = measureBorder(enode,2);
4016 int padding_left = lengthToPx( style->padding[0], width, em ) + DEBUG_TREE_DRAW + measureBorder(enode,3);
4017 int padding_right = lengthToPx( style->padding[1], width, em ) + DEBUG_TREE_DRAW + measureBorder(enode,1);
4018 int padding_top = lengthToPx( style->padding[2], width, em ) + DEBUG_TREE_DRAW + border_top;
4019 int padding_bottom = lengthToPx( style->padding[3], width, em ) + DEBUG_TREE_DRAW + border_bottom;
4020 // If there is a border at top/bottom, the AddLine(padding), which adds the room
4021 // for the border too, should avoid a page break between the node and its border
4022 int padding_top_split_flag = border_top ? RN_SPLIT_AFTER_AVOID : 0;
4023 int padding_bottom_split_flag = border_bottom ? RN_SPLIT_BEFORE_AVOID : 0;
4024
4025 //margin_left += 50;
4026 //margin_right += 50;
4027 // Legacy rendering did/does not support negative margins
4028 if (margin_left < 0) margin_left = 0;
4029 if (margin_right < 0) margin_right = 0;
4030 if (margin_top < 0) margin_top = 0;
4031 if (margin_bottom < 0) margin_bottom = 0;
4032
4033 if (margin_left>0)
4034 x += margin_left;
4035 y += margin_top;
4036
4037 // Support style 'width:' attribute, for specific elements only: solely <HR> for now.
4038 // As crengine does not support many fancy display: styles, and each HTML block
4039 // elements is rendered as a crengine blockElement (an independant full width slice,
4040 // with possibly some margin/padding/indentation/border, of the document height),
4041 // we don't want to waste reading width with blank areas (as we are not sure
4042 // the content producer intended them because of crengine limitations).
4043 css_length_t style_width = style->width;
4044 if (style_width.type != css_val_unspecified) {
4045 // printf("style_width.type: %d (%d)\n", style_width.value, style_width.type);
4046
4047 bool apply_style_width = false; // Don't apply width by default
4048 bool style_width_pct_em_only = true; // only apply if width is in '%' or in 'em'
4049 int style_width_alignment = 0; // 0: left aligned / 1: centered / 2: right aligned
4050 // Uncomment for testing alternate defaults:
4051 // apply_style_width = true; // apply width to all elements (except table elements)
4052 // style_width_pct_em_only = false; // accept any kind of unit
4053
4054 if (enode->getNodeId() == el_hr) { // <HR>
4055 apply_style_width = true;
4056 style_width_alignment = 1; // <hr> are auto-centered
4057 style_width_pct_em_only = false; // width for <hr> is safe, whether px or %
4058 }
4059
4060 // Note: we should not handle css_d_inline_table like css_d_table,
4061 // as it may be used with non-table elements.
4062 // But we might want to handle (css_d_inline_table & el_table) like we
4063 // handle css_d_table here and in the 3 other places below - but after
4064 // some quick thinking (but no check in a browser) it feels we should not,
4065 // and we'd better ensure and apply style width.
4066 if (apply_style_width && style->display >= css_d_table ) {
4067 // table elements are managed elsewhere: we'd rather not mess with the table
4068 // layout algorithm by applying styles width here (even if this algorithm
4069 // is not perfect, it looks like applying width here does not make it better).
4070 apply_style_width = false;
4071 }
4072 if (apply_style_width && style_width_pct_em_only) {
4073 if (style_width.type != css_val_percent && style_width.type != css_val_em) {
4074 apply_style_width = false;
4075 }
4076 }
4077 if (apply_style_width) {
4078 int style_width_px = lengthToPx( style_width, width, em );
4079 if (style_width_px && style_width_px < width) { // ignore if greater than our given width
4080 // printf("style_width: %dps at ~y=%d\n", style_width_px, y);
4081 if (style_width_alignment == 1) { // centered
4082 x += (width - style_width_px)/2;
4083 }
4084 else if (style_width_alignment == 2) { // right aligned
4085 x += (width - style_width_px);
4086 }
4087 width = style_width_px;
4088 }
4089 }
4090 }
4091
4092 bool flgSplit = false;
4093 width -= margin_left + margin_right;
4094 int h = 0;
4095 int pb_flag;
4096 LFormattedTextRef txform;
4097 {
4098 //CRLog::trace("renderBlockElement - creating render accessor");
4099 RenderRectAccessor fmt( enode );
4100 fmt.setX( x );
4101 fmt.setY( y );
4102 fmt.setWidth( width );
4103 fmt.setHeight( 0 );
4104 fmt.push();
4105
4106 if (width <= 0) {
4107 // In case we get a negative width (no room to render and draw anything),
4108 // which may happen in hyper constrained layouts like heavy nested tables,
4109 // don't go further in the rendering code.
4110 // It seems erm_block and erm_final do "survive" such negative width,
4111 // by just keeping substracting margin and padding to this negative
4112 // number until, in ldomNode::renderFinalBlock(), when it's going to
4113 // be serious, it is (luckily?) casted to an unsigned int in:
4114 // int h = f->Format((lUInt16)width, (lUInt16)page_h);
4115 // So, a width=-138 becomes width=65398 and the drawing is then done
4116 // without nearly any width constraint: some text may be drawn, some
4117 // parts clipped, but the user will see something is wrong.
4118 // So, we only do the following for tables, where the rendering code
4119 // is more easily messed up by negative widths. As we won't show
4120 // any table, and we want the user to notice something is missing,
4121 // we set this element rendering method to erm_killed, and
4122 // DrawDocument will then render a small figure...
4123 if (enode->getRendMethod() >= erm_table) {
4124 printf("CRE WARNING: no width to draw %s\n", UnicodeToLocal(ldomXPointer(enode, 0).toString()).c_str());
4125 enode->setRendMethod( erm_killed );
4126 fmt.setHeight( 15 ); // not squared, so it does not look
4127 fmt.setWidth( 10 ); // like a list square bullet
4128 fmt.setX( fmt.getX() - 5 );
4129 // We shift it half to the left, so a bit of it can be
4130 // seen if some element on the right covers it with some
4131 // background color.
4132 return fmt.getHeight();
4133 }
4134 }
4135
4136 int m = enode->getRendMethod();
4137 switch( m )
4138 {
4139 case erm_killed:
4140 {
4141 // DrawDocument will render a small figure in this rect area
4142 fmt.setHeight( 15 ); // not squared, so it does not look
4143 fmt.setWidth( 10 ); // like a list square bullet
4144 return fmt.getHeight();
4145 }
4146 break;
4147 case erm_table:
4148 {
4149 // ??? not sure
4150 if ( isFootNoteBody )
4151 context.enterFootNote( footnoteId );
4152 lvRect r;
4153 enode->getAbsRect(r); // this will get as r.top the absolute Y from
4154 // the relative fmt.setY( y ) we did above.
4155 // Was: if (margin_top>0)
4156 // but add it even if 0-margin to carry node's page-break-before ALWAYS
4157 // or AVOID, so renderTable doesn't have to care about it and can
4158 // use AVOID on its first AddLine()
4159 pb_flag = pagebreakhelper(enode, width);
4160 context.AddLine(r.top - margin_top, r.top, pb_flag);
4161 // (margin_top has already been added to make r.top, so we substracted it here)
4162 // renderTable() deals itself with the table borders and paddings
4163
4164 // We allow a table to shrink width (cells to shrink to their content),
4165 // unless they have a width specified.
4166 // This can be tweaked with:
4167 // table {width: 100% !important} to have tables take the full available width
4168 // table {width: auto !important} to have tables shrink to their content
4169 bool shrink_to_fit = false;
4170 int fitted_width = -1;
4171 int table_width = width;
4172 int specified_width = lengthToPx( style->width, width, em );
4173 if (specified_width <= 0) {
4174 // We get 0 when width unspecified (not set or when "width: auto"):
4175 // use container width, but allow table to shrink
4176 // (Should this only be done when explicit (css_val_unspecified, css_generic_auto)?)
4177 shrink_to_fit = true;
4178 }
4179 else {
4180 if (specified_width > width)
4181 specified_width = width;
4182 table_width = specified_width;
4183 }
4184 int h = renderTable( context, enode, 0, y, table_width, shrink_to_fit, fitted_width );
4185 // Should we really apply a specified height ?!
4186 int st_h = lengthToPx( style->height, em, em );
4187 if ( h < st_h )
4188 h = st_h;
4189 fmt.setHeight( h );
4190 // Update table width if it was fitted/shrunk
4191 if (shrink_to_fit && fitted_width > 0)
4192 table_width = fitted_width;
4193 fmt.setWidth( table_width );
4194 if (table_width < width) {
4195 // See for margin: auto, to center or align right the table
4196 int shift_x = 0;
4197 css_length_t m_left = style->margin[0];
4198 css_length_t m_right = style->margin[1];
4199 bool left_auto = m_left.type == css_val_unspecified && m_left.value == css_generic_auto;
4200 bool right_auto = m_right.type == css_val_unspecified && m_right.value == css_generic_auto;
4201 if (left_auto) {
4202 if (right_auto) { // center align
4203 shift_x = (width - table_width)/2;
4204 }
4205 else { // right align
4206 shift_x = (width - table_width);
4207 }
4208 }
4209 if (shift_x) {
4210 fmt.setX( fmt.getX() + shift_x );
4211 }
4212 }
4213
4214 fmt.push();
4215 enode->getAbsRect(r); // this will get as r.bottom the absolute Y after fmt.setHeight( y )
4216
4217 // Was: if(margin_bottom>0)
4218 // context.AddLine(r.bottom, r.bottom + margin_bottom, RN_SPLIT_AFTER_AUTO);;
4219 // but add it even if 0-margin to carry node's page-break-after ALWAYS
4220 // or AVOID, so renderTable doesn't have to care about it and can
4221 // use AVOID on its last AddLine()
4222 pb_flag = RN_SPLIT_BEFORE_AVOID;
4223 pb_flag |= CssPageBreak2Flags(getPageBreakAfter(enode))<<RN_SPLIT_AFTER;
4224 context.AddLine(r.bottom, r.bottom + margin_bottom, pb_flag);
4225 if ( isFootNoteBody )
4226 context.leaveFootNote();
4227 return h + margin_top + margin_bottom; // return block height
4228 }
4229 break;
4230 case erm_block:
4231 {
4232 if ( isFootNoteBody )
4233 context.enterFootNote( footnoteId );
4234
4235
4236 // recurse all sub-blocks for blocks
4237 int y = padding_top;
4238 int cnt = enode->getChildCount();
4239 lvRect r;
4240 enode->getAbsRect(r);
4241 if (margin_top>0) {
4242 pb_flag = pagebreakhelper(enode, width);
4243 context.AddLine(r.top - margin_top, r.top, pb_flag);
4244 }
4245 if (padding_top>0) {
4246 pb_flag = pagebreakhelper(enode,width) | padding_top_split_flag;
4247 context.AddLine(r.top, r.top + padding_top, pb_flag);
4248 }
4249
4250 // List item marker rendering when css_d_list_item_block
4251 int list_marker_padding = 0; // set to non-zero when list-style-position = outside
4252 int list_marker_height = 0;
4253 if ( style->display == css_d_list_item_block ) {
4254 // list_item_block rendered as block (containing text and block elements)
4255 // Get marker width and height
4256 LFormattedTextRef txform( enode->getDocument()->createFormattedText() );
4257 int list_marker_width;
4258 lString32 marker = renderListItemMarker( enode, list_marker_width, txform.get(), -1, 0);
4259 list_marker_height = txform->Format( (lUInt16)(width - list_marker_width), (lUInt16)enode->getDocument()->getPageHeight() );
4260 if ( style->list_style_position == css_lsp_outside &&
4261 style->text_align != css_ta_center && style->text_align != css_ta_right) {
4262 // When list_style_position = outside, we have to shift the whole block
4263 // to the right and reduce the available width, which is done
4264 // below when calling renderBlockElement() for each child
4265 // Rendering hack: we treat it just as "inside" when text-align "right" or "center"
4266 list_marker_padding = list_marker_width;
4267 }
4268 else if ( style->list_style_type != css_lst_none ) {
4269 // When list_style_position = inside, we need to let renderFinalBlock()
4270 // know there is a marker to prepend when rendering the first of our
4271 // children (or grand-children, depth first) that is erm_final
4272 // (caveat: the marker will not be shown if any of the first children
4273 // is erm_invisible)
4274 // (No need to do anything when list-style-type none.)
4275 ldomNode * tmpnode = enode;
4276 while ( tmpnode && tmpnode->hasChildren() ) {
4277 tmpnode = tmpnode->getChildNode( 0 );
4278 if (tmpnode && tmpnode->getRendMethod() == erm_final) {
4279 // We need renderFinalBlock() to be able to reach the current
4280 // enode when it will render/draw this tmpnode, so it can call
4281 // renderListItemMarker() on it and get a marker formatted
4282 // according to current node style.
4283 // We store enode's data index into the RenderRectAccessor of
4284 // this erm_final tmpnode so it's saved in the cache.
4285 // (We used to use NodeNumberingProps to store it, but it
4286 // is not saved in the cache.)
4287 RenderRectAccessor tmpfmt( tmpnode );
4288 tmpfmt.setListPropNodeIndex( enode->getDataIndex() );
4289 break;
4290 }
4291 }
4292 }
4293 }
4294
4295 int block_height = 0;
4296 for (int i=0; i<cnt; i++)
4297 {
4298 ldomNode * child = enode->getChildNode( i );
4299 if (child) {
4300 if (child->isText()) {
4301 // We may occasionally let empty text nodes among block elements,
4302 // just skip them
4303 lString32 s = child->getText();
4304 if (IsEmptySpace(s.c_str(), s.length()))
4305 continue;
4306 crFatalError(144, "Attempting to render non-empty Text node");
4307 }
4308 //fmt.push();
4309 int h = renderBlockElementLegacy(context, child,
4310 padding_left + list_marker_padding, y,
4311 width - padding_left - padding_right -
4312 list_marker_padding, usable_right_overflow);
4313 y += h;
4314 block_height += h;
4315 }
4316 }
4317 // ensure there's enough height to fully display the list marker
4318 if (list_marker_height && list_marker_height > block_height) {
4319 y += list_marker_height - block_height;
4320 }
4321
4322 int st_y = lengthToPx( style->height, em, em );
4323 if ( y < st_y )
4324 y = st_y;
4325 fmt.setHeight( y + padding_bottom ); //+ margin_top + margin_bottom ); //???
4326
4327 if (margin_top==0 && padding_top==0) {
4328 // If no margin or padding that would have carried the page break above, and
4329 // if this page break was not consumed (it is reset to css_pb_auto when used)
4330 // by any child node and is still there, add an empty line to carry it
4331 pb_flag = pagebreakhelper(enode,width);
4332 if (pb_flag)
4333 context.AddLine(r.top, r.top, pb_flag);
4334 }
4335
4336 lvRect rect;
4337 enode->getAbsRect(rect);
4338 pb_flag = CssPageBreak2Flags(getPageBreakAfter(enode))<<RN_SPLIT_AFTER;
4339 if(padding_bottom>0) {
4340 int p_pb_flag = margin_bottom>0 ? RN_SPLIT_AFTER_AUTO : pb_flag;
4341 p_pb_flag |= padding_bottom_split_flag;
4342 context.AddLine(y + rect.top, y + rect.top + padding_bottom, p_pb_flag);
4343 }
4344 if(margin_bottom>0) {
4345 context.AddLine(y + rect.top + padding_bottom,
4346 y + rect.top + padding_bottom + margin_bottom, pb_flag);
4347 }
4348 if (margin_bottom==0 && padding_bottom==0 && pb_flag) {
4349 // If no margin or padding to carry pb_flag, add an empty line
4350 context.AddLine(y + rect.top, y + rect.top, pb_flag);
4351 }
4352 if ( isFootNoteBody )
4353 context.leaveFootNote();
4354 return y + margin_top + margin_bottom + padding_bottom; // return block height
4355 }
4356 break;
4357 case erm_final:
4358 {
4359
4360 if ( style->display == css_d_list_item_block ) {
4361 // list_item_block rendered as final (containing only text and inline elements)
4362 // Rendering hack: not when text-align "right" or "center", as we treat it just as "inside"
4363 if ( style->list_style_position == css_lsp_outside &&
4364 style->text_align != css_ta_center && style->text_align != css_ta_right) {
4365 // When list_style_position = outside, we have to shift the final block
4366 // to the right and reduce its width
4367 int list_marker_width;
4368 lString32 marker = renderListItemMarker( enode, list_marker_width, NULL, -1, 0 );
4369 fmt.setX( fmt.getX() + list_marker_width );
4370 width -= list_marker_width;
4371 }
4372 }
4373
4374 if ( isFootNoteBody )
4375 context.enterFootNote( footnoteId );
4376 // render whole node content as single formatted object
4377 fmt.setWidth( width );
4378 fmt.setX( fmt.getX() );
4379 fmt.setY( fmt.getY() );
4380 fmt.setLangNodeIndex( 0 ); // No support for lang in legacy rendering
4381 fmt.setUsableRightOverflow(usable_right_overflow); // Partially support of hanging punctuation in legacy mode
4382 fmt.push();
4383 //if ( CRLog::isTraceEnabled() )
4384 // CRLog::trace("rendering final node: %s %d %s", LCSTR(enode->getNodeName()), enode->getDataIndex(), LCSTR(ldomXPointer(enode,0).toString()) );
4385 h = enode->renderFinalBlock( txform, &fmt, width - padding_left - padding_right );
4386 context.updateRenderProgress(1);
4387 // if ( context.updateRenderProgress(1) )
4388 // CRLog::trace("last rendered node: %s %d", LCSTR(enode->getNodeName()), enode->getDataIndex());
4389 #ifdef DEBUG_DUMP_ENABLED
4390 logfile << "\n";
4391 #endif
4392 //int flags = styleToTextFmtFlags( fmt->getStyle(), 0 );
4393 //renderFinalBlock( node, &txform, fmt, flags, 0, 16 );
4394 //int h = txform.Format( width, context.getPageHeight() );
4395 fmt.push();
4396 fmt.setHeight( h + padding_top + padding_bottom );
4397 flgSplit = true;
4398 }
4399 break;
4400 case erm_invisible:
4401 // don't render invisible blocks
4402 return 0;
4403 default:
4404 CRLog::error("Unsupported render method %d", m);
4405 crFatalError(141, "Unsupported render method"); // error
4406 break;
4407 }
4408 }
4409 if ( flgSplit ) {
4410 lvRect rect;
4411 enode->getAbsRect(rect);
4412 // split pages
4413 if ( context.wantsLines() ) {
4414 if (margin_top>0) {
4415 pb_flag = pagebreakhelper(enode,width);
4416 context.AddLine(rect.top - margin_top, rect.top, pb_flag);
4417 }
4418 if (padding_top>0) {
4419 pb_flag = pagebreakhelper(enode,width);
4420 pb_flag |= padding_top_split_flag;
4421 context.AddLine(rect.top, rect.top + padding_top, pb_flag);
4422 }
4423 css_page_break_t before, inside, after;
4424 //before = inside = after = css_pb_auto;
4425 before = getPageBreakBefore( enode );
4426 after = getPageBreakAfter( enode );
4427 inside = getPageBreakInside( enode );
4428
4429 // if (before!=css_pb_auto) {
4430 // CRLog::trace("page break before node %s class=%s text=%s", LCSTR(enode->getNodeName()), LCSTR(enode->getAttributeValue(U"class")), LCSTR(enode->getText(' ', 120) ));
4431 // }
4432
4433 //getPageBreakStyle( enode, before, inside, after );
4434 int break_before = CssPageBreak2Flags( before );
4435 int break_after = CssPageBreak2Flags( after );
4436 int break_inside = CssPageBreak2Flags( inside );
4437 int count = txform->GetLineCount();
4438 int orphans = (int)(style->orphans) - (int)(css_orphans_widows_1) + 1;
4439 int widows = (int)(style->widows) - (int)(css_orphans_widows_1) + 1;
4440 for (int i=0; i<count; i++) {
4441 const formatted_line_t * line = txform->GetLineInfo(i);
4442 int line_flags = 0; //TODO
4443 if (i == 0)
4444 line_flags |= break_before << RN_SPLIT_BEFORE;
4445 else
4446 line_flags |= break_inside << RN_SPLIT_BEFORE;
4447 if (i == count-1 && (padding_bottom + margin_bottom == 0))
4448 line_flags |= break_after << RN_SPLIT_AFTER;
4449 else
4450 line_flags |= break_inside << RN_SPLIT_AFTER;
4451 if (orphans > 1 && i > 0 && i < orphans)
4452 // with orphans:2, and we're the 2nd line (i=1), avoid split before
4453 // so we stick to first line
4454 line_flags |= RN_SPLIT_AVOID << RN_SPLIT_BEFORE;
4455 if (widows > 1 && i < count-1 && count-1 - i < widows)
4456 // with widows:2, and we're the last before last line (i=count-2),
4457 // avoid split after so we stick to last line
4458 line_flags |= RN_SPLIT_AVOID << RN_SPLIT_AFTER;
4459
4460 context.AddLine(rect.top + line->y + padding_top,
4461 rect.top + line->y + line->height + padding_top, line_flags);
4462
4463 if( padding_bottom>0 && i==count-1 ) {
4464 pb_flag = margin_bottom>0 ? RN_SPLIT_AFTER_AUTO :
4465 (CssPageBreak2Flags(getPageBreakAfter(enode)) << RN_SPLIT_AFTER);
4466 pb_flag |= padding_bottom_split_flag;
4467 context.AddLine(rect.bottom - padding_bottom, rect.bottom, pb_flag);
4468 }
4469 if( margin_bottom>0 && i==count-1 ) {
4470 pb_flag = CssPageBreak2Flags(getPageBreakAfter(enode)) << RN_SPLIT_AFTER;
4471 context.AddLine(rect.bottom, rect.bottom + margin_bottom, pb_flag);
4472 }
4473 // footnote links analysis
4474 if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) { // disable footnotes for footnotes
4475 // If paragraph is RTL, we are meeting words in the reverse of the reading order:
4476 // so, insert each link for this line at the same position, instead of at the end.
4477 int link_insert_pos = -1; // append
4478 if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) {
4479 link_insert_pos = context.getCurrentLinksCount();
4480 }
4481 for ( int w=0; w<line->word_count; w++ ) {
4482 // check link start flag for every word
4483 if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) {
4484 const src_text_fragment_t * src = txform->GetSrcInfo( line->words[w].src_text_index );
4485 if ( src && src->object ) {
4486 ldomNode * node = (ldomNode*)src->object;
4487 ldomNode * parent = node->getParentNode();
4488 while (parent && parent->getNodeId() != el_a)
4489 parent = parent->getParentNode();
4490 if ( parent && parent->hasAttribute(LXML_NS_ANY, attr_href ) ) {
4491 // was: && parent->getAttributeValue(LXML_NS_ANY, attr_type ) == "note")
4492 // but we want to be able to gather in-page footnotes by only
4493 // specifying a -cr-hint: to the footnote target, with no need
4494 // to set one to the link itself
4495 lString32 href = parent->getAttributeValue(LXML_NS_ANY, attr_href );
4496 if ( href.length()>0 && href.at(0)=='#' ) {
4497 href.erase(0,1);
4498 context.addLink( href, link_insert_pos );
4499 }
4500 }
4501 }
4502 }
4503 }
4504 }
4505 }
4506 } // wantsLines()
4507 else {
4508 // we still need to gather links when an alternative context is used
4509 // (duplicated part of the code above, as we don't want to consume any page-break)
4510 int count = txform->GetLineCount();
4511 for (int i=0; i<count; i++) {
4512 const formatted_line_t * line = txform->GetLineInfo(i);
4513 if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) {
4514 // If paragraph is RTL, we are meeting words in the reverse of the reading order:
4515 // so, insert each link for this line at the same position, instead of at the end.
4516 int link_insert_pos = -1; // append
4517 if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) {
4518 link_insert_pos = context.getCurrentLinksCount();
4519 }
4520 for ( int w=0; w<line->word_count; w++ ) {
4521 // check link start flag for every word
4522 if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) {
4523 const src_text_fragment_t * src = txform->GetSrcInfo( line->words[w].src_text_index );
4524 if ( src && src->object ) {
4525 ldomNode * node = (ldomNode*)src->object;
4526 ldomNode * parent = node->getParentNode();
4527 while (parent && parent->getNodeId() != el_a)
4528 parent = parent->getParentNode();
4529 if ( parent && parent->hasAttribute(LXML_NS_ANY, attr_href ) ) {
4530 lString32 href = parent->getAttributeValue(LXML_NS_ANY, attr_href );
4531 if ( href.length()>0 && href.at(0)=='#' ) {
4532 href.erase(0,1);
4533 context.addLink( href, link_insert_pos );
4534 }
4535 }
4536 }
4537 }
4538 }
4539 }
4540 }
4541 }
4542 if ( isFootNoteBody )
4543 context.leaveFootNote();
4544 return h + margin_top + margin_bottom + padding_top + padding_bottom;
4545 }
4546 }
4547 else
4548 {
4549 crFatalError(111, "Attempting to render Text node");
4550 }
4551 return 0;
4552 }
4553
4554
4555 //=======================================================================
4556 // FlowState: block formatting context manager
4557 // used by renderBlockElementEnhanced()
4558 //=======================================================================
4559 // Created at the start of a "block formatting context" (one when rendering
4560 // the root node, one when rendering each float or table cell).
4561 // We move down in it as we add elements along the height, and shift
4562 // (and unshift) x/width on nested block elements according to
4563 // elements horizontal margins, borders, paddings and specified or
4564 // computed widths.
4565 // It ensures proper vertical margins collapsing by accumulating added
4566 // vertical margins, and only pushing the resulting margin when some
4567 // real non-margin content is added.
4568 // It also forwards lines/spaces to lvpagesplitter's "context", ensuring
4569 // proper page split flags (and avoiding unwelcome ones).
4570 // Floats are added to it for positioning along this height
4571 // depending on other floats.
4572 // It then can provide a "float footprint" to final nodes, so they can
4573 // lay out their text (and own embedded floats) alongside block floats.
4574 //
4575 // Some initial limitations with floats had later found some solutions,
4576 // which can be enabled by setting some flags. Mentioning them because
4577 // the code might be a bit rooted around these early limitations:
4578 // - Floats added by a block element are "clear"'ed when leaving
4579 // this block: some blank height is added if necessary, so they are
4580 // fully contained in this block (they do not overflow the block
4581 // and are not shown alongside next elements).
4582 // Can be overcome by using BLOCK_RENDERING_DO_NOT_CLEAR_OWN_FLOATS,
4583 // which keeps active floats (and have lvtextfm embedded floats
4584 // forwarded to the main flow): this may cause some issues with
4585 // text selection in such floats, and may need a double drawing
4586 // (background first, content next) for later text blocks to not
4587 // draw their background over already drawn past floats.
4588 // - The footprint provided to final nodes is just a single rectangle
4589 // anchored at the top left and another one anchored at the top right.
4590 // These footprints are stored in the cached RenderRectAccessor (that
4591 // we extended with new slots) of each final node (so we can get it
4592 // back to re-lay out the text when needed, when looking for links,
4593 // searching or selecting text).
4594 // Can be overcome by using BLOCK_RENDERING_ALLOW_EXACT_FLOATS_FOOTPRINTS,
4595 // which, when there are no more than 5 floats involved on a final
4596 // node, will store these floats' node Ids in the slots that would
4597 // otherwise be used for storing the footprint rectangles.
4598 // Allowing that for more than 5 would need new slots, or another
4599 // kind of decicated crengine cache for storing a variable number
4600 // of things related to a node.
4601
4602 #define NO_BASELINE_UPDATE 0x7FFFFFFF
4603
4604 class FlowState {
4605 private:
4606 // BlockShift: backup of some FlowState fields when entering
4607 // an inner block (so, making a sub-level).
4608 class BlockShift { public:
4609 int direction;
4610 lInt32 lang_node_idx;
4611 int x_min;
4612 int x_max;
4613 int usable_overflow_x_min;
4614 int usable_overflow_x_max;
4615 int l_y;
4616 int in_y_min;
4617 int in_y_max;
4618 bool avoid_pb_inside;
reset(int dir,lInt32 langNodeIdx,int xmin,int xmax,int overxmin,int overxmax,int ly,int iymin,int iymax,bool avoidpbinside)4619 void reset(int dir, lInt32 langNodeIdx, int xmin, int xmax, int overxmin, int overxmax, int ly, int iymin, int iymax, bool avoidpbinside) {
4620 direction = dir;
4621 lang_node_idx = langNodeIdx;
4622 x_min = xmin;
4623 x_max = xmax;
4624 usable_overflow_x_min = overxmin;
4625 usable_overflow_x_max = overxmax;
4626 l_y = ly;
4627 in_y_min = iymin;
4628 in_y_max = iymax;
4629 avoid_pb_inside = avoidpbinside;
4630 }
BlockShift(int dir,lInt32 langNodeIdx,int xmin,int xmax,int overxmin,int overxmax,int ly,int iymin,int iymax,bool avoidpbinside)4631 BlockShift(int dir, lInt32 langNodeIdx, int xmin, int xmax, int overxmin, int overxmax, int ly, int iymin, int iymax, bool avoidpbinside) :
4632 direction(dir),
4633 lang_node_idx(langNodeIdx),
4634 x_min(xmin),
4635 x_max(xmax),
4636 usable_overflow_x_min(overxmin),
4637 usable_overflow_x_max(overxmax),
4638 l_y(ly),
4639 in_y_min(iymin),
4640 in_y_max(iymax),
4641 avoid_pb_inside(avoidpbinside)
4642 { }
4643 };
4644 class BlockFloat : public lvRect {
4645 public:
4646 ldomNode * node;
4647 int level; // level that owns this float
4648 int inward_margin; // inner margin (left margin for right floats, right margin for left floats),
4649 // allows knowing how much the main text glyphs and hanging punctuation
4650 // can protrude inside this float (we limit that to the first level margin,
4651 // not including any additional inner padding or margin)
4652 bool is_right;
4653 bool final_pos; // true if y0/y1 are the final absolute position and this
4654 // float should not be moved when pushing vertical margins.
BlockFloat(int x0,int y0,int x1,int y1,bool r,int l,bool f,ldomNode * n=NULL)4655 BlockFloat( int x0, int y0, int x1, int y1, bool r, int l, bool f, ldomNode * n=NULL) :
4656 lvRect(x0,y0,x1,y1),
4657 level(l),
4658 inward_margin(0),
4659 is_right(r),
4660 final_pos(f),
4661 node(n)
4662 {
4663 if (n && n->getChildCount() > 0) {
4664 // The margins were used to position the original
4665 // float node in its wrapping floatBox - so get it
4666 // back from their relative positions
4667 RenderRectAccessor fmt(n->getChildNode(0));
4668 if (is_right)
4669 inward_margin = fmt.getX();
4670 else
4671 inward_margin = (x1 - x0) - (fmt.getX() + fmt.getWidth());
4672 }
4673 }
4674 };
4675 int direction; // flow inline direction (LTR/RTL)
4676 lInt32 lang_node_idx; // dataIndex of nearest upper node with a lang="" attribute (0 if none)
4677 // We don't need to know its value in here, the idx of this node
4678 // will be saved in the final block RenderRectAccessor so it can
4679 // be fetched from the node when needed, when laying out text).
4680 LVRendPageContext & context;
4681 LVPtrVector<BlockShift> _shifts;
4682 LVPtrVector<BlockFloat> _floats;
4683 int rend_flags;
4684 int page_height; // just needed to avoid excessive bogus margins and heights
4685 int level; // current level
4686 int o_width; // initial original container width
4687 int c_y; // current y relative to formatting context top (our absolute y for us here)
4688 int l_y; // absolute y at which current level started
4689 int in_y_min; // min/max children content abs y (for floats in current or inner levels
4690 int in_y_max; // that overflow this level height)
4691 int x_min; // current left min x
4692 int x_max; // current right max x
4693 int usable_overflow_x_min; // current left and right x usable for glyph overflows and hanging punctuation,
4694 int usable_overflow_x_max; // reset when some border or background color change is met
4695 int baseline_req; // baseline type requested (REQ_BASELINE_FOR_INLINE_BLOCK or REQ_BASELINE_FOR_TABLE)
4696 int baseline_y; // baseline y relative to formatting context top (computed when rendering inline-block/table)
4697 bool baseline_set; // (set to true on first baseline met)
4698 bool is_main_flow;
4699 int top_clear_level; // level to attach floats for final clearance when leaving the flow
4700 bool avoid_pb_inside; // To carry this fact from upper elements to inner children
4701 bool avoid_pb_inside_just_toggled_on; // for specific processing of boundaries
4702 bool avoid_pb_inside_just_toggled_off;
4703 bool seen_content_since_page_split; // to avoid consecutive page split when only empty or padding in between
4704 int last_split_after_flag; // in case we need to adjust upcoming line's flag vs previous line's
4705
4706 // vm_* : state of our handling of collapsable vertical margins
4707 bool vm_has_some; // true when some vertical margin added, reset to false when pushed
4708 bool vm_disabled; // for disabling vertical margin handling when in edge case situations
4709 bool vm_target_avoid_pb_inside;
4710 ldomNode * vm_target_node; // target element that will be shifted by the collapsed margin
4711 int vm_target_level; // level of this target node
4712 int vm_active_pb_flag; // page-break flag for the whole margin
4713 int vm_max_positive_margin;
4714 int vm_max_negative_margin;
4715 int vm_back_usable_as_margin; // previously moved vertical space where next margin could be accounted in
4716
4717 public:
FlowState(LVRendPageContext & ctx,int width,int usable_left_overflow,int usable_right_overflow,int rendflags,int dir=REND_DIRECTION_UNSET,lInt32 langNodeIdx=0)4718 FlowState( LVRendPageContext & ctx, int width, int usable_left_overflow, int usable_right_overflow,
4719 int rendflags, int dir=REND_DIRECTION_UNSET, lInt32 langNodeIdx=0 ):
4720 direction(dir),
4721 lang_node_idx(langNodeIdx),
4722 context(ctx),
4723 rend_flags(rendflags),
4724 level(0),
4725 o_width(width),
4726 c_y(0),
4727 l_y(0),
4728 in_y_min(0),
4729 in_y_max(0),
4730 x_min(0),
4731 x_max(width),
4732 baseline_req(REQ_BASELINE_NOT_NEEDED),
4733 baseline_y(0),
4734 baseline_set(false),
4735 avoid_pb_inside(false),
4736 avoid_pb_inside_just_toggled_on(false),
4737 avoid_pb_inside_just_toggled_off(false),
4738 seen_content_since_page_split(false),
4739 last_split_after_flag(RN_SPLIT_AUTO),
4740 vm_has_some(false),
4741 vm_disabled(false),
4742 vm_target_node(NULL),
4743 vm_target_level(0),
4744 vm_max_positive_margin(0),
4745 vm_max_negative_margin(0),
4746 vm_back_usable_as_margin(0),
4747 vm_target_avoid_pb_inside(false),
4748 vm_active_pb_flag(RN_SPLIT_AUTO)
4749 {
4750 is_main_flow = context.getPageList() != NULL;
4751 if ( context.wantsLines() ) {
4752 // Also behave as is_main_flow when context wants lines (which,
4753 // if it is not the main flow, it should want lines only for
4754 // transfering them to the real main flow; the only use case
4755 // for now is when rendering cells in single-column tables).
4756 is_main_flow = true;
4757 }
4758 top_clear_level = is_main_flow ? 1 : 2; // see resetFloatsLevelToTopLevel()
4759 page_height = context.getPageHeight();
4760 usable_overflow_x_min = x_min - usable_left_overflow;
4761 usable_overflow_x_max = x_max + usable_right_overflow;
4762 }
~FlowState()4763 ~FlowState() {
4764 // Shouldn't be needed as these must have been cleared
4765 // by leaveBlockLevel(). But let's ensure we clean up well.
4766 for (int i=_floats.length()-1; i>=0; i--) {
4767 BlockFloat * flt = _floats[i];
4768 _floats.remove(i);
4769 delete flt;
4770 }
4771 for (int i=_shifts.length()-1; i>=0; i--) {
4772 BlockShift * sht = _shifts[i];
4773 _shifts.remove(i);
4774 delete sht;
4775 }
4776 }
4777
isMainFlow()4778 bool isMainFlow() {
4779 return is_main_flow;
4780 }
getDirection()4781 int getDirection() {
4782 return direction;
4783 }
getLangNodeIndex()4784 lInt32 getLangNodeIndex() {
4785 return lang_node_idx;
4786 }
getOriginalContainerWidth()4787 int getOriginalContainerWidth() {
4788 return o_width;
4789 }
getCurrentAbsoluteX()4790 int getCurrentAbsoluteX() {
4791 return x_min;
4792 }
getCurrentAbsoluteY()4793 int getCurrentAbsoluteY() {
4794 return c_y;
4795 }
getCurrentRelativeY()4796 int getCurrentRelativeY() {
4797 return c_y - l_y;
4798 }
getCurrentLevel()4799 int getCurrentLevel() {
4800 return level;
4801 }
getCurrentLevelAbsoluteY()4802 int getCurrentLevelAbsoluteY() {
4803 return l_y;
4804 }
getPageHeight()4805 int getPageHeight() {
4806 return page_height;
4807 }
getPageContext()4808 LVRendPageContext * getPageContext() {
4809 return &context;
4810 }
getAvoidPbInside()4811 bool getAvoidPbInside() {
4812 return avoid_pb_inside;
4813 }
getUsableLeftOverflow()4814 int getUsableLeftOverflow() {
4815 return x_min - usable_overflow_x_min;
4816 }
getUsableRightOverflow()4817 int getUsableRightOverflow() {
4818 return usable_overflow_x_max - x_max;
4819 }
4820 // "sequence" is what elsewhere we've called "flow",
4821 // just changing the name here to make it clear that
4822 // this is not the "flow" of FlowState
newSequence(int nonlinear)4823 void newSequence( int nonlinear ) {
4824 context.newFlow( nonlinear ) ;
4825 }
4826
setRequestedBaselineType(int baseline_req_type)4827 void setRequestedBaselineType(int baseline_req_type) {
4828 baseline_req = baseline_req_type;
4829 }
getBaselineAbsoluteY(ldomNode * node=NULL)4830 int getBaselineAbsoluteY(ldomNode * node=NULL) {
4831 // Quotes from https://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align
4832 // Note that our table rendering code has not been updated to use FlowState,
4833 // so, if top element is a table, we haven't got any baseline.
4834 if ( baseline_req == REQ_BASELINE_FOR_TABLE ) {
4835 // "The baseline of an 'inline-table' is the baseline of the first
4836 // row of the table.
4837 // Tests show that this is true even if the element with
4838 // display: inline-table is not iself a table, but has a table
4839 // as a child. But if there's any text non-table before the table,
4840 // the baseline for that text is used.
4841 // So, we should check for a table even when we have baseline_set
4842 // and a baseline_y. The returned baseline will be the smallest
4843 // of the two.
4844 //
4845 // Try to find the first table row, looking at descendants of the
4846 // provided node (which must be the top node of this FlowState).
4847 //
4848 // Note: this is still valid with ruby internal wrapping in
4849 // a table: even if we switched the 2 first rows in the internal
4850 // structures used when rendering the table, we look here at the
4851 // DOM, where the base text is still the first erm_table_row.
4852 //
4853 // Walk the tree up and down (avoid the need for recursion):
4854 ldomNode * n = node;
4855 ldomNode * rowNode = NULL;
4856 if ( n && n->getChildCount() > 0 ) {
4857 int nextChildIndex = 0;
4858 n = n->getChildNode(nextChildIndex);
4859 while ( true ) {
4860 // Check the node only the first time we meet it
4861 // (nextChildIndex == 0) and not when we get back
4862 // to it from a child to process next sibling
4863 if ( nextChildIndex == 0 ) {
4864 if ( n->getRendMethod() == erm_table_row ) {
4865 rowNode = n;
4866 break; // found the first row
4867 }
4868 }
4869 // Process next child, but don't go look into erm_final nodes
4870 // (they may contain other inline-tables with rows which have
4871 // already contributed to set the final node baseline that
4872 // was accounted in baseline_y when it did addContentLine()).
4873 if ( n->getRendMethod() != erm_final && nextChildIndex < n->getChildCount() ) {
4874 n = n->getChildNode(nextChildIndex);
4875 nextChildIndex = 0;
4876 continue;
4877 }
4878 // No more child, get back to parent and have it process our sibling
4879 nextChildIndex = n->getNodeIndex() + 1;
4880 n = n->getParentNode();
4881 if ( n == node && nextChildIndex >= n->getChildCount() )
4882 break; // back to top node and all its children visited
4883 }
4884 }
4885 if ( rowNode ) {
4886 // Get this row baseline y related to the top node y
4887 RenderRectAccessor fmt( rowNode );
4888 int row_baseline = fmt.getY() + fmt.getBaseline();
4889 ldomNode * n = rowNode->getParentNode();
4890 for (; n && n!=node; n=n->getParentNode()) {
4891 RenderRectAccessor fmt(n);
4892 row_baseline += fmt.getY();
4893 }
4894 if ( !baseline_set || (row_baseline < baseline_y) ) {
4895 baseline_y = row_baseline;
4896 }
4897 baseline_set = true;
4898 }
4899 }
4900 if ( !baseline_set ) {
4901 // "The baseline of an 'inline-block' is the baseline of its last line
4902 // box in the normal flow, unless it has either no in-flow line boxes
4903 // [or ...], in which case the baseline is the bottom margin edge."
4904 // So, return what will be returned as height just after this
4905 // function is called in renderBlockElement().
4906 return getCurrentAbsoluteY();
4907 }
4908 return baseline_y;
4909 }
4910
hasActiveFloats()4911 bool hasActiveFloats() {
4912 return _floats.length() > 0;
4913 }
hasFloatRunningAtY(int y,int h=0)4914 bool hasFloatRunningAtY( int y, int h=0 ) {
4915 for (int i=0; i<_floats.length(); i++) {
4916 BlockFloat * flt = _floats[i];
4917 if (flt->top < y+h && flt->bottom > y) {
4918 return true;
4919 }
4920 }
4921 return false;
4922 }
getFloatsCurrentShifts(int & dx_left,int & dx_right,int h=0)4923 void getFloatsCurrentShifts( int & dx_left, int & dx_right, int h=0 ) {
4924 // Initial work in absolute coordinates
4925 int left_x = 0;
4926 int right_x = o_width;
4927 for (int i=0; i<_floats.length(); i++) {
4928 BlockFloat * flt = _floats[i];
4929 if (flt->top < c_y+h && flt->bottom > c_y) {
4930 if ( flt->is_right && flt->left < right_x )
4931 right_x = flt->left;
4932 if ( !flt->is_right && flt->right > left_x )
4933 left_x = flt->right;
4934 }
4935 }
4936 // And adjust to current container's width
4937 dx_left = left_x - x_min;
4938 dx_right = x_max - right_x;
4939 }
4940
addSpaceToContext(int starty,int endy,int line_h,bool split_avoid_before,bool split_avoid_inside,bool split_avoid_after)4941 void addSpaceToContext( int starty, int endy, int line_h,
4942 bool split_avoid_before, bool split_avoid_inside, bool split_avoid_after ) {
4943 // Add vertical space by adding multiple lines of height line_h
4944 // (as an alternative to adding a single huge line), ensuring
4945 // given split_avoid flags, and avoiding split on the floats met
4946 // along the way
4947 if (endy - starty <= 0)
4948 return;
4949 int line_dir_flag = direction == REND_DIRECTION_RTL ? RN_LINE_IS_RTL : 0;
4950 // Ensure avoid_pb_inside
4951 if ( avoid_pb_inside_just_toggled_off ) {
4952 avoid_pb_inside_just_toggled_off = false;
4953 if ( !split_avoid_before && !hasFloatRunningAtY(starty) ) {
4954 // Previous added line may have RN_SPLIT_AFTER_AVOID, but
4955 // we want to allow a split between it and this new line:
4956 // just add an empty line to cancel the split avoid
4957 context.AddLine( starty, starty, RN_SPLIT_BOTH_AUTO|line_dir_flag );
4958 last_split_after_flag = RN_SPLIT_AUTO;
4959 }
4960 }
4961 if ( avoid_pb_inside ) {
4962 // Update provided flags to split avoid
4963 if ( avoid_pb_inside_just_toggled_on ) {
4964 avoid_pb_inside_just_toggled_on = false;
4965 // previous added line may allow a break after,
4966 // let split_avoid_before unchanged
4967 }
4968 else {
4969 split_avoid_before = true;
4970 }
4971 split_avoid_inside = true;
4972 split_avoid_after = true;
4973 }
4974
4975 if (line_h <= 0) // sanity check
4976 line_h = 1;
4977 bool is_first = true;
4978 bool is_last = false;
4979 int flags = 0;;
4980 int y0 = starty;
4981 while (y0 < endy) {
4982 int y1 = y0 + line_h;
4983 if (y1 >= endy) {
4984 y1 = endy;
4985 is_last = true;
4986 }
4987 flags = 0;
4988 if ( split_avoid_before && is_first )
4989 flags |= RN_SPLIT_BEFORE_AVOID;
4990 if ( split_avoid_after && is_last )
4991 flags |= RN_SPLIT_AFTER_AVOID;
4992 if ( split_avoid_inside && !is_first & !is_last )
4993 flags |= RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
4994 if ( hasFloatRunningAtY(y0) )
4995 flags |= RN_SPLIT_BEFORE_AVOID;
4996 flags |= line_dir_flag;
4997 context.AddLine(y0, y1, flags);
4998 y0 = y1;
4999 is_first = false;
5000 }
5001 last_split_after_flag = RN_GET_SPLIT_AFTER(flags);
5002 }
5003
addContentLine(int height,int flags,int baseline=NO_BASELINE_UPDATE,bool is_padding=false)5004 int addContentLine( int height, int flags, int baseline=NO_BASELINE_UPDATE, bool is_padding=false ) {
5005 // As we may push vertical margins, we return the total height moved
5006 // (needed when adding bottom padding that may push inner vertical
5007 // margins, which should be accounted in the element height).
5008 int start_c_y = c_y;
5009 int line_dir_flag = direction == REND_DIRECTION_RTL ? RN_LINE_IS_RTL : 0;
5010 // Ensure avoid_pb_inside
5011 if ( avoid_pb_inside_just_toggled_off ) {
5012 avoid_pb_inside_just_toggled_off = false;
5013 // Previous added line may have RN_SPLIT_AFTER_AVOID,
5014 // but we want to allow a split between it and this new
5015 // line or the coming pushed vertical margin:
5016 // just add an empty line to cancel the split avoid
5017 if ( !(flags & RN_SPLIT_BEFORE_AVOID) && !hasFloatRunningAtY(c_y) ) {
5018 context.AddLine( c_y, c_y, RN_SPLIT_BOTH_AUTO|line_dir_flag );
5019 last_split_after_flag = RN_SPLIT_AUTO;
5020 }
5021 }
5022 if ( avoid_pb_inside ) {
5023 if ( avoid_pb_inside_just_toggled_on ) {
5024 avoid_pb_inside_just_toggled_on = false;
5025 // Previous added line may allow a break after, so dont prevent it
5026 flags = RN_GET_SPLIT_BEFORE(flags) | RN_SPLIT_AFTER_AVOID;
5027 }
5028 else {
5029 flags = RN_SPLIT_BOTH_AVOID;
5030 }
5031 }
5032 // Push vertical margin with active pb
5033 if ( vm_has_some ) { // avoid expensive call if not needed
5034 pushVerticalMargin(RN_GET_SPLIT_BEFORE(flags));
5035 }
5036 else if ( BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS) && _floats.length()>0 ) {
5037 // but this has to be done if not done by pushVerticalMargin()
5038 resetFloatsLevelToTopLevel();
5039 }
5040 // Most often for content lines, lvtextfm.cpp's LVFormatter will
5041 // have already checked for float (via BlockFloatFootprint), so
5042 // avoid calling hasFloatRunningAtY() when not needed
5043 if ( !(flags & RN_SPLIT_BEFORE_AVOID) && hasFloatRunningAtY(c_y) )
5044 flags |= RN_SPLIT_BEFORE_AVOID;
5045 flags |= line_dir_flag;
5046 context.AddLine( c_y, c_y + height, flags );
5047 last_split_after_flag = RN_GET_SPLIT_AFTER(flags);
5048 if ( !is_padding )
5049 seen_content_since_page_split = true;
5050 moveDown( height );
5051 if ( vm_disabled ) // Re-enable it now that some real content has been seen
5052 enableVerticalMargin();
5053 if ( baseline_req != REQ_BASELINE_NOT_NEEDED ) {
5054 // We don't update last baseline when adding padding or when none is provided
5055 if ( !is_padding && baseline != NO_BASELINE_UPDATE ) {
5056 // https://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align
5057 // "The baseline of an 'inline-table' is the baseline of the first row
5058 // of the table.
5059 // The baseline of an 'inline-block' is the baseline of its last line
5060 // box in the normal flow, unless it has either no in-flow line boxes
5061 // [or ...], in which case the baseline is the bottom margin edge."
5062 // Also see for some interesting sample snippet:
5063 // https://stackoverflow.com/questions/19352072/what-is-the-difference-between-inline-block-and-inline-table/56305302#56305302
5064 // A tricky thing with inline-table is that the baseline is different if
5065 // there is a table of if there's not
5066 // - if the first content is a table row, it should be the baseline of
5067 // the row (which might not be the baseline of the first or last line
5068 // of any table cell of that row).
5069 // - if the first content is not a table row (we can have inline-table
5070 // containing no table-like elements), it is the real baseline of the
5071 // first line in that content.
5072 // As the rendering table code does not use FlowState, we manage the
5073 // first case in getBaselineAbsoluteY() when we're done.
5074 if ( baseline_req == REQ_BASELINE_FOR_TABLE && baseline_set ) {
5075 // inline-table: first baseline already met, it will stay the final baseline
5076 }
5077 else { // inline-block
5078 // We update it from c_y which accounts for any vertical margins
5079 // pushed // when adding this line:
5080 baseline_y = c_y - height + baseline;
5081 if ( !baseline_set)
5082 baseline_set = true;
5083 }
5084 }
5085 }
5086 return c_y - start_c_y;
5087 }
5088
addContentSpace(int height,int line_h,bool split_avoid_before,bool split_avoid_inside,bool split_avoid_after)5089 void addContentSpace( int height, int line_h, bool split_avoid_before,
5090 bool split_avoid_inside, bool split_avoid_after ) {
5091 // Push vertical margin with active pb
5092 if ( vm_has_some ) { // avoid expensive call if not needed
5093 pushVerticalMargin(split_avoid_before ? RN_SPLIT_AVOID : RN_SPLIT_AUTO);
5094 }
5095 else if ( BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS) && _floats.length()>0 ) {
5096 // but this has to be done if not done by pushVerticalMargin()
5097 resetFloatsLevelToTopLevel();
5098 }
5099 addSpaceToContext( c_y, c_y + height, line_h, split_avoid_before, split_avoid_inside, split_avoid_after);
5100 last_split_after_flag = split_avoid_after ? RN_SPLIT_AVOID : RN_SPLIT_AUTO;
5101 if ( !seen_content_since_page_split ) {
5102 // Assume that if split_avoid_inside is not set, this space
5103 // is just padding/style_h. We want to prevent double
5104 // page splits inside such spaces.
5105 if ( split_avoid_inside ) {
5106 seen_content_since_page_split = true;
5107 }
5108 }
5109 moveDown( height );
5110 if ( vm_disabled ) // Re-enable it now that some real content has been seen
5111 enableVerticalMargin();
5112 }
5113
disableVerticalMargin()5114 void disableVerticalMargin() {
5115 vm_disabled = true;
5116 // Reset everything so it's ready when re-enabled, and our various
5117 // tests act as if there's no vertical margin to handle.
5118 vm_has_some = false;
5119 vm_target_node = NULL;
5120 vm_target_avoid_pb_inside = false;
5121 vm_target_level = 0;
5122 vm_max_positive_margin = 0;
5123 vm_max_negative_margin = 0;
5124 vm_active_pb_flag = RN_SPLIT_AUTO;
5125 vm_back_usable_as_margin = 0;
5126 }
enableVerticalMargin()5127 void enableVerticalMargin() {
5128 vm_disabled = false;
5129 // Also drop any back margin added since disabling
5130 vm_back_usable_as_margin = 0;
5131 }
addVerticalMargin(ldomNode * node,int height,int pb_flag,bool is_top_margin=false)5132 void addVerticalMargin( ldomNode * node, int height, int pb_flag, bool is_top_margin=false ) {
5133 if ( vm_disabled ) { // ignore that margin, unless it asks for a page split
5134 if ( pb_flag != RN_SPLIT_ALWAYS ) {
5135 return;
5136 }
5137 enableVerticalMargin();
5138 }
5139 // printf(" adding vertical margin %d (%x top=%d)\n", height, pb_flag, is_top_margin);
5140
5141 // When collapsing margins, the resulting margin is to be
5142 // applied outside the first element involved. So, remember
5143 // the first node we are given, that will later be shifted down
5144 // when some real content is added (padding/border or text).
5145 if ( is_top_margin ) { // new node
5146 if ( is_main_flow && !vm_target_node && vm_active_pb_flag == RN_SPLIT_ALWAYS ) {
5147 // We have only met bottom margins before, and one had "break-after: always":
5148 // push what we had till now
5149 pushVerticalMargin();
5150 }
5151 // If we get a new node with a same or lower level, we left our previous
5152 // node and it didn't have any content nor height: we can just let it
5153 // be where it was and continue with the new node, which will get the
5154 // accumulated margin.
5155 // (This is a wrong, but simpler to do. The right way would be to
5156 // grab down the previous empty nodes with the new final target node.)
5157 if ( !vm_target_node || level <= vm_target_level ) {
5158 vm_target_node = node;
5159 vm_target_level = level;
5160 // Remember avoid_pb_inside for the target node
5161 vm_target_avoid_pb_inside = avoid_pb_inside;
5162 if ( BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS) && _floats.length()>0 ) {
5163 // Reset level of all previous floats as they are
5164 // correctly positioned and should not be moved
5165 // if this (new) vm_target_node get some margin
5166 // and is moved down.
5167 resetFloatsLevelToTopLevel();
5168 }
5169 }
5170 }
5171
5172 // We may combine multiple margins, each with its own flag, and we should
5173 // compute a global flag for the future single collapsed margin resulting
5174 // from them.
5175 // Quotes from https://www.w3.org/TR/CSS2/page.html#allowed-page-breaks:
5176 // "Page breaks can occur at the following places: [...] 1. In the
5177 // vertical margin between block-level boxes
5178 // Rule A: Breaking at (1) is allowed only if the 'page-break-after' and
5179 // 'page-break-before' properties of all the elements generating boxes
5180 // that meet at this margin allow it, which is when at least one of
5181 // them has the value 'always', 'left', or 'right', or when all of them
5182 // are 'auto'."
5183 // So, a page break "always" is stronger than "avoid" ("avoid" seems to be
5184 // just a way to not set "auto", so breaking the rule "when all of them
5185 // are 'auto'", but it is allowed when "at least one of them has the
5186 // value 'always'")
5187 // Note: if we wanted to avoid a page break between siblings H2 and H3,
5188 // we should use in our epub.css: H2 + H3 { page-break-before: avoid; }
5189
5190 if ( pb_flag == RN_SPLIT_ALWAYS ) {
5191 // Avoid consecutive page split when no real content in between
5192 if ( BLOCK_RENDERING(rend_flags, ALLOW_PAGE_BREAK_WHEN_NO_CONTENT) || seen_content_since_page_split ) {
5193 if ( vm_active_pb_flag != RN_SPLIT_ALWAYS ) {
5194 // First break-before or break-after:always seen.
5195 // Forget any previously seen margin, as it would
5196 // collapse at end of previous page, but we won't
5197 // add a line for it.
5198 vm_max_positive_margin = 0;
5199 vm_max_negative_margin = 0;
5200 // Also forget height taken while clearing floats
5201 vm_back_usable_as_margin = 0;
5202 // Also don't have the one provided added, if it's some
5203 // bottom margin that comes with break-after:always.
5204 if ( !is_top_margin )
5205 height = 0;
5206 }
5207 vm_active_pb_flag = RN_SPLIT_ALWAYS;
5208 }
5209 }
5210 else if ( pb_flag == RN_SPLIT_AVOID ) {
5211 if ( vm_active_pb_flag != RN_SPLIT_ALWAYS )
5212 vm_active_pb_flag = RN_SPLIT_AVOID;
5213 }
5214 else { // auto
5215 // Keep current vm_active_pb_flag (RN_SPLIT_AUTO if nothing else seen).
5216 // But:
5217 // Rule B: "However, if all of them are 'auto' and a common ancestor of
5218 // all the elements has a 'page-break-inside' value of 'avoid',
5219 // then breaking here is not allowed"
5220 // As it is the target node that will get the margin, if avoid_pb_inside
5221 // was set for that node, we should avoid a split in that margin.
5222 if ( vm_target_avoid_pb_inside )
5223 vm_active_pb_flag = RN_SPLIT_AVOID;
5224 }
5225
5226 if ( vm_active_pb_flag == RN_SPLIT_ALWAYS ) {
5227 // Also from https://www.w3.org/TR/CSS2/page.html#allowed-page-breaks:
5228 // "When a forced page break occurs here, the used value of the relevant
5229 // 'margin-bottom' property is set to '0'; the relevant 'margin-top'
5230 // [...] applies (i.e., is not set to '0') after a forced page break."
5231 // Not certain what should be the "relevant" margin-top when collapsing
5232 // multiple margins, but as we are going to push the collapsed margin
5233 // with RN_SPLIT_BEFORE_ALWAYS, it feels any bottom margin should be
5234 // ignored (so a large previous bottom margin does not make a larger
5235 // top margin on this new page).
5236 if ( !is_top_margin )
5237 height = 0; // Make that bottom margin be empty
5238 }
5239
5240 // The collapsed margin will be the max of the positive ones + the min
5241 // of the negative ones (the most negative one)
5242 if (height > 0 && height > vm_max_positive_margin)
5243 vm_max_positive_margin = height;
5244 if (height < 0 && height < vm_max_negative_margin)
5245 vm_max_negative_margin = height;
5246 vm_has_some = true;
5247
5248 if ( !is_top_margin && vm_target_node && node == vm_target_node && vm_active_pb_flag == RN_SPLIT_ALWAYS ) {
5249 // We leave our target node, and some pb always was set by it
5250 // or its children, but with no content nor height.
5251 // It looks like we should ensure the pb always, possibly making
5252 // an empty page if upcoming node has a pb always too (that's
5253 // what Prince seems to do).
5254 pushVerticalMargin();
5255 }
5256 if ( !BLOCK_RENDERING(rend_flags, COLLAPSE_VERTICAL_MARGINS) ) {
5257 // If we don't want collapsing margins, just push this added
5258 // unit of margin (not really checked if we get exactly what
5259 // we would get when in legacy rendering mode).
5260 pushVerticalMargin();
5261 }
5262 }
getCurrentVerticalMargin()5263 int getCurrentVerticalMargin() {
5264 // The collapsed margin is the max of the positive ones + the min
5265 // of the negative ones (the most negative one)
5266 int margin = vm_max_positive_margin + vm_max_negative_margin;
5267 if ( is_main_flow && margin < 0 ) {
5268 // Unless requested, we don't allow negative margins in the
5269 // main flow, as it could have us moving backward and cause
5270 // issues with page splitting and text selection.
5271 if ( !BLOCK_RENDERING(rend_flags, ALLOW_NEGATIVE_COLLAPSED_MARGINS) ) {
5272 margin = 0;
5273 }
5274 }
5275 if ( margin > 0 && vm_back_usable_as_margin > 0 ) {
5276 if ( margin > vm_back_usable_as_margin )
5277 margin -= vm_back_usable_as_margin;
5278 else
5279 margin = 0;
5280 }
5281 // Even if below, we can have some margin lines discarded in page mode,
5282 // super huge bogus margins could still be shown in scroll mode (and footer
5283 // in scroll mode could still show "page 1 of 2" while we scroll this margin
5284 // height that spans many many screen heights).
5285 // To avoid any confusion, limit any margin to be the page height
5286 if ( margin > page_height )
5287 margin = page_height;
5288 return margin;
5289 }
pushVerticalMargin(int next_split_before_flag=RN_SPLIT_AUTO)5290 void pushVerticalMargin( int next_split_before_flag=RN_SPLIT_AUTO ) {
5291 if ( vm_disabled )
5292 return;
5293 int line_dir_flag = direction == REND_DIRECTION_RTL ? RN_LINE_IS_RTL : 0;
5294
5295 // If this is a node at level 0 (root node, floatBox, inlineBox) or
5296 // level 1 (body, floatBox or inlineBox single child), drop any back
5297 // margin as we should get its full bottom margin (we should be called
5298 // twice: for the top margin, and there is not back margin yet - and
5299 // for the bottom margin, where there may be).
5300 if (level <= (is_main_flow ? 0 : 1))
5301 vm_back_usable_as_margin = 0;
5302 // Compute the single margin to add along our flow y and to pages context.
5303 int margin = getCurrentVerticalMargin();
5304 vm_back_usable_as_margin = 0;
5305 // printf("pushing vertical margin %d (%x %d)\n", margin, vm_target_node, vm_target_level);
5306
5307 // Note: below, we allow some margin (previous page margin) to be discarded
5308 // if it can not fit on the previous page and is pushed on next page. This is
5309 // usually fine when we have white backgrounds, but may show some white holes
5310 // at bottom of page before a split (where a non-fully discarded margin would
5311 // otherwise allow some now-white background to be painted on the whole page).
5312
5313 if (is_main_flow && margin < 0) { // can only happen if ALLOW_NEGATIVE_COLLAPSED_MARGINS
5314 // We're moving backward and don't know what was before and what's
5315 // coming next. Add an empty line to avoid a split there.
5316 context.AddLine( c_y, c_y, RN_SPLIT_BOTH_AVOID|line_dir_flag );
5317 }
5318 else if (is_main_flow) {
5319 // When this is called, whether we have some positive resulting vertical
5320 // margin or not (zero), we need to context.AddLine() a possibly empty
5321 // line with the right pb flags according to what we gathered while
5322 // vertical margins were added.
5323 int flags;
5324 bool emit_empty = true;
5325 if ( vm_active_pb_flag == RN_SPLIT_ALWAYS ) {
5326 // Forced page break
5327 // Quotes from https://www.w3.org/TR/CSS2/page.html#allowed-page-breaks:
5328 // "In the normal flow, page breaks can occur at the following places:
5329 // 1) In the vertical margin between block-level boxes. [...]
5330 // When a forced page break occurs here, the used value of the relevant
5331 // 'margin-bottom' property is set to '0'; the relevant 'margin-top'
5332 // [...] applies (i.e., is not set to '0') after a forced page break."
5333 if ( vm_target_node ) {
5334 // As top margin should be on the next page, split before the margin
5335 // (Note that Prince does not do this: the top margin of an element
5336 // with "page-break-before: always" is discarded.)
5337 flags = RN_SPLIT_BEFORE_ALWAYS | RN_SPLIT_AFTER_AVOID;
5338 }
5339 else { // no target node: only bottom margin with some break-after
5340 // If it does not fit on previous page, and is pushed on next page,
5341 // discard it.
5342 // (This fixes weasyprint/margin-collapse-160.htm "Margins should not
5343 // propagate into the next page", which has a margin-bottom: 1000em
5344 // that would make 37 pages - but these 1000em would still be ensured
5345 // in scroll mode if we were not limiting a margin to max page_height.)
5346 flags = RN_SPLIT_BEFORE_AUTO | RN_SPLIT_AFTER_ALWAYS | RN_SPLIT_DISCARD_AT_START;
5347 }
5348 // Note: floats must have been cleared before calling us if a page-break
5349 // has to happen.
5350 // (We could have cleared all floats here, so they are not truncated and
5351 // split on the new page, but it might be too late: uncleared floats
5352 // would be accounted in a BlockFloatFootprint, which would have some
5353 // impact on the content lines; these lines would be added, and could
5354 // call pushVerticalMargin(): we would here clear floats, but the
5355 // lines would have already been sized with the footprint, which would
5356 // not be there if we clear the floats here, and we would get holes in
5357 // the text.)
5358 }
5359 else if (vm_active_pb_flag == RN_SPLIT_AVOID ) { // obvious
5360 flags = RN_SPLIT_BEFORE_AVOID | RN_SPLIT_AFTER_AVOID;
5361 }
5362 else {
5363 // Allow an unforced page break, keeping the margin on previous page
5364 //
5365 // Quotes from https://www.w3.org/TR/CSS2/page.html#allowed-page-breaks:
5366 // "In the normal flow, page breaks can occur at the following places:
5367 // 3) Between the content edge of a block container box and the
5368 // outer edges of its child content (margin edges of block-level
5369 // children or line box edges for inline-level children) if there
5370 // is a (non-zero) gap between them"
5371 // Which means it is never allowed between a parent's top and its
5372 // first child's top - and between a last child's bottom and its
5373 // parent's bottom (except when some CSS "height:" is into play
5374 // (which we deal with elsewhere), or when CSS "position: relative"
5375 // is used, that we don't support).
5376 //
5377 // "1) In the vertical margin between block-level boxes. When an
5378 // unforced page break occurs here, the used values of the relevant
5379 // 'margin-top' and 'margin-bottom' properties are set to '0'.
5380 // When a forced page break occurs here, the used value of the relevant
5381 // 'margin-bottom' property is set to '0'; the relevant 'margin-top'
5382 // used value may either be set to '0' or retained."
5383 // Which means it is only allowed between a block's bottom and its
5384 // following sibling's top.
5385 //
5386 // We can ensure both of the above cases by just adding a line for
5387 // the margin (even if empty) forwarding the flags of previous and
5388 // next lines.
5389 // As everywhere, we push top paddings (and the first line of text) with
5390 // RN_SPLIT_BEFORE_AUTO and RN_SPLIT_AFTER_AVOID - and bottom paddings
5391 // (and the last line of text) with RN_SPLIT_BEFORE_AVOID and
5392 // RN_SPLIT_AFTER_AUTO:
5393 // - if we are pushing the margin between an upper block and a
5394 // child we'll be between outer padding_top with RN_SPLIT_AFTER_AVOID
5395 // and inner padding_top with RN_SPLIT_BEFORE_AUTO: by adding our
5396 // margin with RN_SPLIT_BEFORE_AUTO and RN_SPLIT_AFTER_AVOID, we
5397 // forbid a page split at this place
5398 // - if we are pushing the margin between same-level sibling blocks: the
5399 // first block padding_bottom has RN_SPLIT_AFTER_AUTO and the next
5400 // block padding_top has RN_SPLIT_BEFORE_AUTO: by using _AUTO on both
5401 // sides of our margin, a split is then allowed.
5402 //
5403 if ( last_split_after_flag == RN_SPLIT_AUTO && next_split_before_flag == RN_SPLIT_AUTO ) {
5404 // Allow a split on both sides.
5405 // Note: if out of break-inside avoid, addContentLine() will have added
5406 // an empty line to get rid of any previous SPLIT_AFTER_AVOID, so this margin
5407 // line can be either at end of previous page, or at start of next page, and
5408 // it will not drag the previous real line with it on next page.
5409 // Allow it to be discarded if it happens to be put at start of next page.
5410 flags = RN_SPLIT_BOTH_AUTO | RN_SPLIT_DISCARD_AT_START;
5411 }
5412 else {
5413 // Just forward the flags (but not ALWAYS, which we should not do again)
5414 int after_flag = last_split_after_flag & ~RN_SPLIT_ALWAYS;
5415 flags = next_split_before_flag | (after_flag << RN_SPLIT_AFTER);
5416 }
5417 // No need to emit these empty lines when split auto and there is no margin
5418 emit_empty = false;
5419 }
5420 if ( margin > 0 || emit_empty ) {
5421 if ( c_y == 0 ) {
5422 // First margin with no content yet. Just avoid a split
5423 // with futur content, so that if next content is taller
5424 // than page height, we don't get an empty first page.
5425 flags &= ~RN_SPLIT_AFTER_ALWAYS;
5426 flags |= RN_SPLIT_AFTER_AVOID;
5427 }
5428 if ( hasFloatRunningAtY(c_y, margin) ) {
5429 // Don't discard margin, or we would discard some part of a float
5430 flags &= ~RN_SPLIT_DISCARD_AT_START;
5431 }
5432 if ( hasFloatRunningAtY(c_y) ) {
5433 // Avoid a split
5434 flags &= ~RN_SPLIT_BEFORE_ALWAYS;
5435 flags |= RN_SPLIT_BEFORE_AVOID;
5436 }
5437 else if ( RN_GET_SPLIT_BEFORE(flags) == RN_SPLIT_ALWAYS && last_split_after_flag == RN_SPLIT_AVOID ) {
5438 // If last line ended with RN_SPLIT_AVOID, it would
5439 // prevent our RN_SPLIT_ALWAYS to have effect.
5440 // It seems that per-specs, the SPLIT_ALWAYS should win.
5441 // So, kill the SPLIT_AVOID with an empty line.
5442 context.AddLine( c_y, c_y, RN_SPLIT_BOTH_AUTO|line_dir_flag );
5443 // Note: keeping the RN_SPLIT_AVOID could help avoiding
5444 // consecutive page splits in some normal cases (we send
5445 // SPLIT_BEFORE_ALWAYS with SPLIT_AFTER_AVOID, and top
5446 // paddings come with SPLIT_AFTER_AVOID). We could
5447 // use !seen_content_since_page_split here to not
5448 // send this empty line, but that's somehow handled
5449 // above where we just not use RN_SPLIT_ALWAYS if it
5450 // hasn't been reset.
5451 }
5452 flags |= line_dir_flag;
5453 context.AddLine( c_y, c_y + margin, flags );
5454 // Note: we don't use AddSpace, a margin does not have to be arbitrarily
5455 // splitted, RN_SPLIT_DISCARD_AT_START ensures it does not continue
5456 // on next page.
5457 // But, if there's a float at start and another at end, we could think
5458 // about trying to find a split point inside the margin (if 2 floats
5459 // are meeting there).
5460 last_split_after_flag = RN_GET_SPLIT_AFTER(flags);
5461 if ( (RN_GET_SPLIT_BEFORE(flags) == RN_SPLIT_ALWAYS) || (RN_GET_SPLIT_AFTER(flags) == RN_SPLIT_ALWAYS) )
5462 // Reset this, so we can ignore some upcoming duplicate SPLIT_ALWAYS
5463 seen_content_since_page_split = false;
5464 }
5465 }
5466 // No need to do any more stuff if margin == 0
5467 if ( margin != 0 ) {
5468 if (vm_target_node) {
5469 // As we are adding the margin above vm_target_node, so actually pushing
5470 // this node down, all its child blocks will also be moved down.
5471 // We need to correct all the l_y of all the sublevels since vm_target_node
5472 // (not including vm_target_level, which contains/is outside vm_target_node):
5473 if (level > vm_target_level) { // current one (not yet in _shifts)
5474 l_y += margin;
5475 in_y_min += margin;
5476 in_y_max += margin;
5477 }
5478 for (int i=level-1; i>vm_target_level; i--) {
5479 _shifts[i]->l_y += margin;
5480 _shifts[i]->in_y_min += margin;
5481 _shifts[i]->in_y_max += margin;
5482 }
5483 // We also need to update sub-levels' floats' absolute or
5484 // relative coordinates
5485 for (int i=0; i<_floats.length(); i++) {
5486 BlockFloat * flt = _floats[i];
5487 if ( flt->level > vm_target_level ) {
5488 if ( flt->final_pos ) {
5489 // Float already absolutely positioned (block float):
5490 // adjust its relative y to its container's top to
5491 // counteract the margin shift
5492 RenderRectAccessor fmt( flt->node );
5493 fmt.setY( fmt.getY() - margin );
5494 }
5495 else {
5496 // Float not absolutely positioned (forwarded embedded float):
5497 // update its absolute coordinates as its container will be
5498 // moved by this margin, so will its floats.
5499 flt->top += margin;
5500 flt->bottom += margin;
5501 }
5502 }
5503 }
5504 // Update the vm_target_node y (its relative y in its container)
5505 RenderRectAccessor fmt( vm_target_node );
5506 #ifdef DEBUG_BLOCK_RENDERING
5507 if (isMainFlow()) {
5508 printf("pushedVM %d => c_y=%d>%d target=%s y=%d>%d\n", margin, c_y, c_y+margin,
5509 UnicodeToLocal(ldomXPointer(vm_target_node, 0).toString()).c_str(),
5510 fmt.getY(), fmt.getY()+margin);
5511 }
5512 #endif
5513 fmt.setY( fmt.getY() + margin );
5514 fmt.push();
5515 // It feels we don't need to update margin_top in the ->style of this
5516 // node to its used value (which would be complicated), as its only
5517 // other use (apart from here) is in:
5518 // - ldomNode::getSurroundingAddedHeight() : where we'll then get a
5519 // little smaller that screen height for full screen images
5520 // - ldomNode::elementFromPoint() : we tweaked it so it does not
5521 // look at margins in enhanced rendering mode.
5522 }
5523 #ifdef DEBUG_BLOCK_RENDERING
5524 else { if (isMainFlow()) printf("pushedVM %d => c_y=%d>%d no target node\n", margin, c_y, c_y+margin); }
5525 #endif
5526 // If no target node (bottom margin), moveDown(margin) is all there
5527 // is to do.
5528 moveDown( margin );
5529 // (We have to do this moveDown() here, after the above floats'
5530 // coordinates updates, otherwise these floats might be removed
5531 // by moveDown() as it passes them.)
5532 }
5533 #ifdef DEBUG_BLOCK_RENDERING
5534 else { if (isMainFlow()) printf("pushedVM 0 => c_y=%d\n", c_y); }
5535 #endif
5536
5537 // Reset everything, as we used it all, and nothing should have
5538 // any impact on the next vertical margin.
5539 // (No need to update vm_target_level which makes no
5540 // sense without a vm_target_node, and we wouldn't
5541 // know which value to reset to.)
5542 vm_target_node = NULL;
5543 vm_target_avoid_pb_inside = false;
5544 vm_max_positive_margin = 0;
5545 vm_max_negative_margin = 0;
5546 vm_active_pb_flag = RN_SPLIT_AUTO;
5547 vm_has_some = false;
5548 if ( BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS) && _floats.length()>0 ) {
5549 // We did not clear floats, so we may meet them again when
5550 // pushing some upcoming vertical margin. But we don't want
5551 // to have them shifted down, as their positions are now fixed.
5552 // Just reset their level to be the top flow clear level, so
5553 // we ignore them in next pushVerticalMargin() calls.
5554 resetFloatsLevelToTopLevel();
5555 }
5556 }
resetFloatsLevelToTopLevel()5557 void resetFloatsLevelToTopLevel() {
5558 // To be called only if DO_NOT_CLEAR_OWN_FLOATS (moved
5559 // as a function because we should call it too when we
5560 // can skip pushVerticalMargin()).
5561 // As blocks are not clearing their own floats, and
5562 // we may meet previous siblings' floats, we don't
5563 // want to have them shifted again by some later
5564 // vertical margin.
5565 // So, assign them to a lower level, the one that
5566 // should have them cleared when exiting the flow
5567 // (to account them in the flow height):
5568 // is_main_flow ? 0 : 1, plus 1
5569 for (int i=0; i<_floats.length(); i++) {
5570 BlockFloat * flt = _floats[i];
5571 flt->level = top_clear_level;
5572 }
5573 }
5574
5575 // Enter/leave a block level: backup/restore some of this FlowState
5576 // fields, and do some housekeeping.
newBlockLevel(int width,int d_left,int usable_overflow_reset_left,int usable_overflow_reset_right,bool avoid_pb,int dir,lInt32 langNodeIdx)5577 void newBlockLevel( int width, int d_left, int usable_overflow_reset_left, int usable_overflow_reset_right,
5578 bool avoid_pb, int dir, lInt32 langNodeIdx ) {
5579 // Don't new/delete to avoid too many malloc/free, keep and re-use/reset
5580 // the ones already created
5581 if ( _shifts.length() <= level ) {
5582 _shifts.push( new BlockShift( direction, lang_node_idx,
5583 x_min, x_max, usable_overflow_x_min, usable_overflow_x_max,
5584 l_y, in_y_min, in_y_max, avoid_pb_inside ) );
5585 }
5586 else {
5587 _shifts[level]->reset( direction, lang_node_idx,
5588 x_min, x_max, usable_overflow_x_min, usable_overflow_x_max,
5589 l_y, in_y_min, in_y_max, avoid_pb_inside );
5590 }
5591 direction = dir;
5592 if (langNodeIdx != -1)
5593 lang_node_idx = langNodeIdx;
5594 x_min += d_left;
5595 x_max = x_min + width;
5596 if ( usable_overflow_reset_left >= 0 ) // -1 means: don't reset, keep previous level limits
5597 usable_overflow_x_min = x_min - usable_overflow_reset_left;
5598 if ( usable_overflow_reset_right >= 0 )
5599 usable_overflow_x_max = x_max + usable_overflow_reset_right;
5600 l_y = c_y;
5601 in_y_min = c_y;
5602 in_y_max = c_y;
5603 level++;
5604 // Don't disable any upper avoid_pb_inside
5605 if ( avoid_pb ) {
5606 if ( !avoid_pb_inside)
5607 avoid_pb_inside_just_toggled_on = true;
5608 avoid_pb_inside = true;
5609 }
5610 }
leaveBlockLevel(int & top_overflow,int & bottom_overflow)5611 int leaveBlockLevel( int & top_overflow, int & bottom_overflow ) {
5612 int start_c_y = l_y;
5613 int last_c_y = c_y;
5614 top_overflow = in_y_min < start_c_y ? start_c_y - in_y_min : 0; // positive value
5615 bottom_overflow = in_y_max > last_c_y ? in_y_max - last_c_y : 0; // positive value
5616 BlockShift * prev = _shifts[level-1];
5617 direction = prev->direction;
5618 lang_node_idx = prev->lang_node_idx;
5619 x_min = prev->x_min;
5620 x_max = prev->x_max;
5621 usable_overflow_x_min = prev->usable_overflow_x_min;
5622 usable_overflow_x_max = prev->usable_overflow_x_max;
5623 l_y = prev->l_y;
5624 in_y_min = in_y_min < prev->in_y_min ? in_y_min : prev->in_y_min; // keep sublevel's one if smaller
5625 in_y_max = in_y_max > prev->in_y_max ? in_y_max : prev->in_y_max; // keep sublevel's one if larger
5626 if ( prev->avoid_pb_inside != avoid_pb_inside )
5627 avoid_pb_inside_just_toggled_off = true;
5628 avoid_pb_inside = prev->avoid_pb_inside;
5629 level--;
5630 int height; // height of the block level we are leaving, that we should return
5631 if ( BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS) && level > (is_main_flow ? 0 : 1) ) {
5632 // If requested, don't clear own floats.
5633 // But we need to for level 0 (root node, floatBox) and
5634 // level 1 (body, floating child) to get them accounted
5635 // in the document or float height.
5636 height = last_c_y - start_c_y;
5637 }
5638 else {
5639 // Otherwise, clear the floats that were added in this shift level
5640 int new_c_y = c_y;
5641 for (int i=_floats.length()-1; i>=0; i--) {
5642 BlockFloat * flt = _floats[i];
5643 if (flt->level > level) {
5644 if (flt->bottom > new_c_y) {
5645 new_c_y = flt->bottom; // move to the bottom of this float
5646 }
5647 // We can't remove/delete them yet, we need them
5648 // to be here for addSpaceToContext() to ensure
5649 // no page split over them
5650 }
5651 }
5652 // addSpaceToContext() will take care of avoiding page split
5653 // where some (non-cleared) floats are still running.
5654 addSpaceToContext(last_c_y, new_c_y, 1, false, false, false);
5655 int dy = new_c_y - last_c_y;
5656 moveDown( dy );
5657 if ( bottom_overflow > dy )
5658 bottom_overflow = bottom_overflow - dy;
5659 else
5660 bottom_overflow = 0;
5661 if (dy > 0)
5662 seen_content_since_page_split = true;
5663 // The vertical space moved to clear floats can be
5664 // deduced from upcoming pushed/collapsed vertical margin.
5665 vm_back_usable_as_margin += dy;
5666 // moveDown() should have already cleared out past floats,
5667 // but just ensure there's none left past the level we left
5668 for (int i=_floats.length()-1; i>=0; i--) {
5669 BlockFloat * flt = _floats[i];
5670 if (flt->level > level) {
5671 _floats.remove(i);
5672 delete flt;
5673 }
5674 }
5675 height = new_c_y - start_c_y;
5676 }
5677 return height;
5678 }
5679
moveDown(int dy)5680 bool moveDown( int dy ) {
5681 // Will return true if we had running floats *before* moving
5682 int prev_c_y = c_y;
5683 if (dy > 0) { // moving forward
5684 c_y += dy;
5685 if ( c_y > in_y_max ) {
5686 // update current level max seen y (not really needed as it is
5687 // checked on leaveBlockLeve, but for symetry)
5688 in_y_max = c_y;
5689 }
5690 }
5691 else if (dy < 0) { // moving backward
5692 // Only allowed if we are not the main flow (eg: a float), or if
5693 // explicitely requested (as it may cause issues with page splitting
5694 // and text/links selection)
5695 if ( !is_main_flow || BLOCK_RENDERING(rend_flags, ALLOW_NEGATIVE_COLLAPSED_MARGINS) ) {
5696 c_y += dy;
5697 if ( c_y < in_y_min ) {
5698 // update current level min seen y (in case negative margins
5699 // moved past level origin y)
5700 in_y_min = c_y;
5701 }
5702 }
5703 // Nothing else to do, no float cleaning as we did not move forward
5704 return true; // pretend we have floats running
5705 }
5706 // Clear past floats, and see if we have some running
5707 bool had_floats_running = false;
5708 for (int i=_floats.length()-1; i>=0; i--) {
5709 BlockFloat * flt = _floats[i];
5710 if ( flt->top < prev_c_y && flt->bottom > prev_c_y ) {
5711 had_floats_running = true;
5712 // If a float has started strictly before prev_c_y, it was
5713 // running (if it started on prev_c_y, it was a new one
5714 // and should not prevent a page split at prev_c_y)
5715 }
5716 if (flt->bottom <= c_y) {
5717 // This float is past, we shouldn't have to worry
5718 // about it anymore
5719 _floats.remove(i);
5720 delete flt;
5721 }
5722 }
5723 return had_floats_running;
5724 }
5725
clearFloats(css_clear_t clear)5726 void clearFloats( css_clear_t clear ) {
5727 if (clear <= css_c_none)
5728 return;
5729 int cleared_y = c_y;
5730 for (int i=0; i<_floats.length(); i++) {
5731 BlockFloat * flt = _floats[i];
5732 if ( ( clear == css_c_both ) || ( clear == css_c_left && !flt->is_right ) ||
5733 ( clear == css_c_right && flt->is_right ) ) {
5734 if (flt->bottom > cleared_y)
5735 cleared_y = flt->bottom;
5736 }
5737 }
5738 int dy = cleared_y - c_y;
5739 // Add the vertical space skipped to the page splitting context.
5740 // addSpaceToContext() will take care of avoiding page split
5741 // where some (non-cleared) floats are still running.
5742 addSpaceToContext(c_y, cleared_y, 1, false, false, false);
5743 if (dy > 0) {
5744 moveDown( dy ); // will delete past floats
5745 // The vertical space moved to clear floats can be
5746 // deduced from upcoming pushed/collapsed vertical margin.
5747 vm_back_usable_as_margin += dy;
5748 seen_content_since_page_split = true;
5749 }
5750 if ( vm_disabled ) {
5751 // Re-enable vertical margin (any clear, even if not "both",
5752 // mean we could have moved and we are not tied to stay
5753 // aligned with 0-height floats).
5754 // Not sure about this, but it allows working around the issue
5755 // with 0-height float containers that we disable margin with, by
5756 // just (with styletweaks) setting "clear: both" on their followup.
5757 enableVerticalMargin();
5758 }
5759 }
5760
5761 // Floats positioning helpers. These work in absolute coordinates (relative
5762 // to flow state initial top)
5763 // Returns min y for next float
getNextFloatMinY(css_clear_t clear)5764 int getNextFloatMinY(css_clear_t clear) {
5765 int y = c_y; // current line y
5766 for (int i=0; i<_floats.length(); i++) {
5767 BlockFloat * flt = _floats[i];
5768 // A later float should never be positioned above an earlier float
5769 if ( flt->top > y )
5770 y = flt->top;
5771 if ( clear > css_c_none) {
5772 if ( (clear == css_c_both) || (clear == css_c_left && !flt->is_right)
5773 || (clear == css_c_right && flt->is_right) ) {
5774 if (flt->bottom > y)
5775 y = flt->bottom;
5776 }
5777 }
5778 }
5779 return y;
5780 }
5781 // Returns next y after start_y where required_width is available over required_height
5782 // Also set offset_x to the x where that width is available
getYWithAvailableWidth(int start_y,int required_width,int required_height,int min_x,int max_x,int & offset_x,bool get_right_offset_x=false)5783 int getYWithAvailableWidth(int start_y, int required_width, int required_height,
5784 int min_x, int max_x, int & offset_x, bool get_right_offset_x=false) {
5785 if (_floats.length() == 0) { // fast path
5786 // If no floats, width is available at start_y
5787 if (get_right_offset_x) {
5788 offset_x = max_x - required_width;
5789 if (offset_x < min_x) // overflow
5790 offset_x = min_x;
5791 }
5792 else {
5793 offset_x = min_x;
5794 }
5795 return start_y;
5796 }
5797 bool fit = false;
5798 int fit_left_x = min_x;
5799 int fit_right_x = max_x;
5800 int fit_top_y = start_y;
5801 int fit_bottom_y = start_y;
5802 int y = start_y;
5803 int floats_max_bottom = start_y;
5804 while (true) {
5805 int left_x = min_x;
5806 int right_x = max_x;
5807 for (int i=0; i<_floats.length(); i++) {
5808 BlockFloat * flt = _floats[i];
5809 if (flt->bottom > floats_max_bottom)
5810 floats_max_bottom = flt->bottom;
5811 if (flt->top <= y && flt->bottom > y) {
5812 if (flt->is_right) {
5813 if (flt->left < right_x)
5814 right_x = flt->left;
5815 }
5816 else {
5817 if (flt->right > left_x)
5818 left_x = flt->right;
5819 }
5820 }
5821 }
5822 if (right_x - left_x < required_width) { // doesn't fit
5823 fit = false;
5824 }
5825 else { // required_width fits at this y
5826 if (!fit) { // first y that fit
5827 fit = true;
5828 fit_top_y = y;
5829 fit_bottom_y = y;
5830 fit_left_x = left_x;
5831 fit_right_x = right_x;
5832 }
5833 else { // we already fitted on previous y
5834 // Adjust to previous boundaries
5835 if (left_x > fit_left_x)
5836 fit_left_x = left_x;
5837 if (right_x < fit_right_x)
5838 fit_right_x = right_x;
5839 if (fit_right_x - fit_left_x < required_width) { // we don't fit anymore
5840 fit = false;
5841 }
5842 else { // we continue fitting
5843 fit_bottom_y = y;
5844 }
5845 }
5846 }
5847 if ( fit && (fit_bottom_y - fit_top_y >= required_height) )
5848 break; // found & done
5849 y += 1;
5850 if ( y >= floats_max_bottom )
5851 break; // no more floats
5852 }
5853 if (!fit) {
5854 // If we left the loop non fitting, because of no or no more floats,
5855 // adjust to provided boundaries
5856 fit_left_x = min_x;
5857 fit_right_x = max_x;
5858 fit_top_y = y;
5859 }
5860 if (get_right_offset_x) {
5861 offset_x = fit_right_x - required_width;
5862 if (offset_x < fit_left_x) // overflow
5863 offset_x = fit_left_x;
5864 }
5865 else {
5866 offset_x = fit_left_x; // We don't mind it it overflows max-min
5867 }
5868 return fit_top_y;
5869 }
addFloat(ldomNode * node,css_clear_t clear,bool is_right,int top_margin)5870 void addFloat( ldomNode * node, css_clear_t clear, bool is_right, int top_margin ) {
5871 RenderRectAccessor fmt( node );
5872 int width = fmt.getWidth();
5873 int height = fmt.getHeight();
5874 int x = fmt.getX(); // a floatBox has no margin and no padding, but x carries the container's padding left
5875 int y = fmt.getY(); // (but y must be =0, as padding_top has already been accounted in c_y
5876 // printf(" block addFloat w=%d h=%d x=%d y=%d\n", width, height, x, y);
5877 int shift_x = 0;
5878 int shift_y = 0;
5879
5880 // Get this float position in our flow state absolute coordinates
5881 int pos_y = c_y + top_margin; // y with collapsed vertical margin included
5882 int fy = getNextFloatMinY(clear); // min_y depending on other floats
5883 if (pos_y > fy)
5884 fy = pos_y;
5885 int fx = 0;
5886 fy = getYWithAvailableWidth(fy, width, height, x_min, x_max, fx, is_right);
5887 _floats.push( new BlockFloat( fx, fy, fx + width, fy + height, is_right, level, true, node) );
5888
5889 // Get relative coordinates to current container top
5890 shift_x = fx - x_min;
5891 shift_y = fy - l_y;
5892 // Set the float relative coordinate in its container
5893 fmt.setX(x + shift_x);
5894 fmt.setY(y + shift_y);
5895 if (is_right)
5896 RENDER_RECT_SET_FLAG(fmt, FLOATBOX_IS_RIGHT);
5897 else
5898 RENDER_RECT_UNSET_FLAG(fmt, FLOATBOX_IS_RIGHT);
5899 // printf(" block addFloat => %d %d > x=%d y=%d\n", shift_x, shift_y, fmt.getX(), fmt.getY());
5900
5901 // If this float overflows the current in_y_min/max, update them
5902 if ( fy - fmt.getTopOverflow() < in_y_min )
5903 in_y_min = fy - fmt.getTopOverflow();
5904 if ( fy + height + fmt.getBottomOverflow() > in_y_max )
5905 in_y_max = fy + height + fmt.getBottomOverflow();
5906 }
5907
addPositionedFloat(int rel_x,int rel_y,int width,int height,int is_right,ldomNode * node)5908 void addPositionedFloat( int rel_x, int rel_y, int width, int height, int is_right, ldomNode * node ) {
5909 int fx = x_min + rel_x;
5910 // Where addPositionedFloat is used (rendering erm_final), c_y has not
5911 // yet been updated, so it is still the base for rel_y.
5912 int fy = c_y + rel_y;
5913 // These embedded floats are kind of in a sublevel of current level
5914 // (even if we didn't create a sublevel), let's add them with level+1
5915 // so they get correctly shifted when vertical margins collapse
5916 // in an outer level from the final node they come from.
5917 _floats.push( new BlockFloat( fx, fy, fx + width, fy + height, is_right, level+1, false, node ) );
5918
5919 // No need to update this level in_y_min/max with this float,
5920 // as it belongs to some erm_final block that will itself
5921 // carry its floats' own overflows, and forward them to
5922 // the current level with next methods.
5923 }
5924 // For erm_final nodes to forward their overflow to current level
updateCurrentLevelTopOverflow(int top_overflow)5925 void updateCurrentLevelTopOverflow(int top_overflow) {
5926 if ( top_overflow <= 0 )
5927 return;
5928 int y = c_y - top_overflow;
5929 if ( y < in_y_min )
5930 in_y_min = y;
5931 }
updateCurrentLevelBottomOverflow(int bottom_overflow)5932 void updateCurrentLevelBottomOverflow(int bottom_overflow) {
5933 if ( bottom_overflow <= 0 )
5934 return;
5935 int y = c_y + bottom_overflow;
5936 if ( y > in_y_max )
5937 in_y_max = y;
5938 }
5939
getFloatFootprint(ldomNode * node,int d_left,int d_right,int d_top)5940 BlockFloatFootprint getFloatFootprint(ldomNode * node, int d_left, int d_right, int d_top ) {
5941 // Returns the footprint of current floats over a final block
5942 // to be laid out at current c_y (+d_top).
5943 // This footprint will be a set of floats to represent outer
5944 // floats possibly having some impact over the final block
5945 // about to be formatted.
5946 // These floats can be either:
5947 // - real floats rectangles, when they are no more than 5
5948 // and ALLOW_EXACT_FLOATS_FOOTPRINTS is enabled
5949 // - or "fake" floats ("footprints") embodying all floats
5950 // in 2 rectangles (one at top left, one at top right),
5951 // and 2 empty floats to represent lower outer floats not
5952 // intersecting with this final block, but whose y sets
5953 // the minimal y for the possible upcoming embedded floats.
5954 //
5955 // Why at most "5" real floats?
5956 // Because I initially went with the "fake" floats solution,
5957 // because otherwise, storing references (per final block) to
5958 // a variable number of other floatBox nodes would need another
5959 // kind of crengine cache, and that looked complicated...
5960 // This "fake" floats way is quite limited, making holes in
5961 // the text, preventing the "staircase" effect of differently
5962 // sized floats.
5963 // Very later, I realized that in the fields I added to
5964 // RenderRectAccessor (extra1...extra5) to store these fake
5965 // floats rectangles, I could store in them the dataIndex of
5966 // the real floatBoxes node (which I can if they are no more
5967 // than 5), so we can fetch their real positions and dimensions
5968 // each time a final block is to be (re-)formatted, to allow
5969 // for a nicer layout of text around these (at most 5) floats.
5970
5971 // We need erm_final at level 1 (body, floatBox or inlineBox child)
5972 // to clear their own floats, to get them accounted in the document,
5973 // float, or inlineBox height, so they are fully contained in it and
5974 // don't overflow. (Level 0 can't be erm_final).
5975 bool no_clear_own_floats = (level > 1) && BLOCK_RENDERING(rend_flags, DO_NOT_CLEAR_OWN_FLOATS);
5976 BlockFloatFootprint footprint = BlockFloatFootprint( this, d_left, d_top, no_clear_own_floats);
5977 if (_floats.length() == 0) // zero footprint if no float
5978 return footprint;
5979 int top_y = c_y + d_top;
5980 int left_x = x_min + d_left;
5981 int right_x = x_max - d_right;
5982 int final_width = right_x - left_x;
5983 // Absolute coordinates of this box top left and top right
5984 int flprint_left_x = left_x;
5985 int flprint_left_y = top_y;
5986 int flprint_right_x = right_x;
5987 int flprint_right_y = top_y;
5988 // Bottomest top of all our current floats (needed to know the absolute
5989 // minimal y at which next left or right float can be positioned)
5990 int flprint_left_min_y = top_y;
5991 int flprint_right_min_y = top_y;
5992 // Extend them to include any part of floats that overlap it.
5993 // We can store at max 5 ldomNode IDs in a RenderRectAccessor
5994 // extra1..extra5 fields. If we meet more than that, we
5995 // will fall back to footprints.
5996 int floats_involved = 0;
5997 // printf("left_x = x_min %d + d_left %d = %d top_y=%d\n", x_min, d_left, left_x, top_y);
5998 for (int i=0; i<_floats.length(); i++) {
5999 BlockFloat * flt = _floats[i];
6000 if ( BLOCK_RENDERING(rend_flags, ALLOW_EXACT_FLOATS_FOOTPRINTS) ) {
6001 // Ignore floats already passed by and possibly not yet removed
6002 if (flt->bottom > top_y) {
6003 if (floats_involved < 5) { // at most 5 slots
6004 // Do the following even if we end up seeing more
6005 // than 5 floats and not using all that.
6006 // Store their dataIndex directly in footprint
6007 footprint.floatIds[floats_involved] = flt->node->getDataIndex();
6008 // printf(" flt #%d x %d y %d\n", i, flt->left, flt->top);
6009
6010 // Compute the transferable floats as it is less expensive
6011 // to do now than using generateEmbeddedFloatsFromFloatIds().
6012 // Have them clip'ed to our top and width (seems to work
6013 // without clipping, but just to be sure as the lvtextfm.cpp
6014 // code was made with assuming rect are fully contained
6015 // in its own working area).
6016 int x0 = flt->left - left_x;
6017 if ( x0 < 0 )
6018 x0 = 0;
6019 else if ( x0 > final_width )
6020 x0 = final_width;
6021 int x1 = flt->right - left_x;
6022 if ( x1 < 0 )
6023 x1 = 0;
6024 else if ( x1 > final_width )
6025 x1 = final_width;
6026 int y0 = flt->top > top_y ? flt->top - top_y : 0;
6027 int y1 = flt->bottom - top_y;
6028 footprint.floats[floats_involved][0] = x0; // x
6029 footprint.floats[floats_involved][1] = y0; // y
6030 footprint.floats[floats_involved][2] = x1 - x0; // width
6031 footprint.floats[floats_involved][3] = y1 - y0; // height
6032 footprint.floats[floats_involved][4] = flt->is_right;
6033 footprint.floats[floats_involved][5] = flt->inward_margin;
6034 }
6035 floats_involved++;
6036 }
6037 }
6038 // Compute the other fields even if we end up not using them
6039 if (flt->is_right) {
6040 if ( flt->left < flprint_right_x ) {
6041 flprint_right_x = flt->left;
6042 }
6043 if ( flt->bottom > flprint_right_y ) {
6044 flprint_right_y = flt->bottom;
6045 }
6046 if ( flt->top > flprint_right_min_y ) {
6047 flprint_right_min_y = flt->top;
6048 }
6049 }
6050 else {
6051 if ( flt->right > flprint_left_x ) {
6052 flprint_left_x = flt->right;
6053 }
6054 if ( flt->bottom > flprint_left_y ) {
6055 flprint_left_y = flt->bottom;
6056 }
6057 if ( flt->top > flprint_left_min_y ) {
6058 flprint_left_min_y = flt->top;
6059 }
6060 }
6061 }
6062 if ( floats_involved > 0 && floats_involved <= 5) {
6063 // We can use floatIds
6064 footprint.use_floatIds = true;
6065 footprint.nb_floatIds = floats_involved;
6066 // No need to call generateEmbeddedFloatsFromFloatIds() as we
6067 // already computed them above.
6068 /* Uncomment for checking reproducible results (here and below)
6069 footprint.generateEmbeddedFloatsFromFloatIds( node, final_width );
6070 */
6071 footprint.floats_cnt = floats_involved;
6072 }
6073 else {
6074 // In case we met only past floats not yet removed, that made
6075 // no impact on flprint_*, all this will result in zero-values
6076 // rects that will make no embedded floats.
6077 footprint.use_floatIds = false;
6078 // Get widths and heights of floats overlapping this final block
6079 footprint.left_h = flprint_left_y - top_y;
6080 footprint.right_h = flprint_right_y - top_y;
6081 footprint.left_w = flprint_left_x - (x_min + d_left);
6082 footprint.right_w = x_max - d_right - flprint_right_x;
6083 if (footprint.left_h < 0 )
6084 footprint.left_h = 0;
6085 if (footprint.right_h < 0 )
6086 footprint.right_h = 0;
6087 if (footprint.left_w < 0 )
6088 footprint.left_w = 0;
6089 if (footprint.right_w < 0 )
6090 footprint.right_w = 0;
6091 footprint.left_min_y = flprint_left_min_y - top_y;
6092 footprint.right_min_y = flprint_right_min_y - top_y;
6093 if (footprint.left_min_y < 0 )
6094 footprint.left_min_y = 0;
6095 if (footprint.right_min_y < 0 )
6096 footprint.right_min_y = 0;
6097 // Generate the float to transfer
6098 footprint.generateEmbeddedFloatsFromFootprints( final_width );
6099 }
6100 return footprint;
6101 }
6102
6103 }; // Done with FlowState
6104
6105 // Register overflowing embedded floats into the main flow
forwardOverflowingFloat(int x,int y,int w,int h,bool r,ldomNode * node)6106 void BlockFloatFootprint::forwardOverflowingFloat( int x, int y, int w, int h, bool r, ldomNode * node )
6107 {
6108 if ( flow == NULL )
6109 return;
6110 flow->addPositionedFloat( d_left + x, d_top + y, w, h, r, node );
6111 // Also update used_min_y and used_max_y, so they can be fetched
6112 // to update erm_final block's top_overflow and bottom_overflow
6113 // if some floats did overflow
6114 RenderRectAccessor fmt( node );
6115 if (y - fmt.getTopOverflow() < used_min_y)
6116 used_min_y = y - fmt.getTopOverflow();
6117 if (y + h + fmt.getBottomOverflow() > used_max_y)
6118 used_max_y = y + h + fmt.getBottomOverflow();
6119 }
6120
generateEmbeddedFloatsFromFloatIds(ldomNode * node,int final_width)6121 void BlockFloatFootprint::generateEmbeddedFloatsFromFloatIds( ldomNode * node, int final_width )
6122 {
6123 // We need to compute the footprints from the already computed
6124 // RenderRectAccessor of the current node, and all the floats
6125 // that were associated to the node because of their involvement
6126 // in text layout.
6127 lvRect rc;
6128 node->getAbsRect( rc, true ); // get formatted text abs coordinates
6129 int node_x = rc.left;
6130 int node_y = rc.top;
6131 floats_cnt = 0;
6132 for (int i=0; i<nb_floatIds; i++) {
6133 ldomNode * fbox = node->getDocument()->getTinyNode(floatIds[i]); // get node from its dataIndex
6134 // The floatBox rect values should be exactly the same as what was
6135 // used in the flow's _floats when rendering. We can check if this
6136 // is not the case (so a bug) by uncommenting a few things below.
6137 RenderRectAccessor fmt(fbox);
6138 fbox->getAbsRect( rc );
6139 /* Uncomment for checking reproducible results:
6140 int bf0, bf1, bf2, bf3, bf4;
6141 bf0=floats[floats_cnt][0]; bf1=floats[floats_cnt][1]; bf2=floats[floats_cnt][2];
6142 bf3=floats[floats_cnt][3]; bf4=floats[floats_cnt][4];
6143 */
6144 // clip them
6145 int x0 = rc.left - node_x;
6146 if ( x0 < 0 )
6147 x0 = 0;
6148 else if ( x0 > final_width )
6149 x0 = final_width;
6150 int x1 = rc.right - node_x;
6151 if ( x1 < 0 )
6152 x1 = 0;
6153 else if ( x1 > final_width )
6154 x1 = final_width;
6155 int y0 = rc.top > node_y ? rc.top - node_y : 0;
6156 int y1 = rc.bottom - node_y;
6157 // Sanity check to avoid negative width or height:
6158 if ( y1 < y0 ) { int ytmp = y0; y0 = y1 ; y1 = ytmp; }
6159 if ( x1 < x0 ) { int xtmp = x0; x0 = x1 ; x1 = xtmp; }
6160 floats[floats_cnt][0] = x0; // x
6161 floats[floats_cnt][1] = y0; // y
6162 floats[floats_cnt][2] = x1 - x0; // width
6163 floats[floats_cnt][3] = y1 - y0; // height
6164 bool is_right = RENDER_RECT_HAS_FLAG(fmt, FLOATBOX_IS_RIGHT);
6165 floats[floats_cnt][4] = is_right;
6166 int inward_margin = 0;
6167 if ( fbox->getChildCount() > 0 ) {
6168 RenderRectAccessor fmt(fbox->getChildNode(0));
6169 if ( is_right )
6170 inward_margin = fmt.getX();
6171 else
6172 inward_margin = (x1 - x0) - (fmt.getX() + fmt.getWidth());
6173 }
6174 floats[floats_cnt][5] = inward_margin;
6175 /* Uncomment for checking reproducible results:
6176 if (x1 < x0) printf("!!!! %d %d %d %d\n", rc.left, rc.right, rc.top, rc.bottom);
6177 if ( bf0!=floats[floats_cnt][0] || bf1!=floats[floats_cnt][1] || bf2!=floats[floats_cnt][2] ||
6178 bf3!=floats[floats_cnt][3] || bf4!=floats[floats_cnt][4] ) {
6179 printf("node_x=%d node_y=%d\n", node_x, node_y);
6180 printf(" fbox #%d x=%d y=%d\n", i+1, rc.left, rc.top);
6181 printf("floatIds flt|abs mismatch: %d|%d %d|%d %d|%d %d|%d %d|%d txt:%s flt:%s \n",
6182 bf0, floats[floats_cnt][0], bf1, floats[floats_cnt][1], bf2, floats[floats_cnt][2],
6183 bf3, floats[floats_cnt][3], bf4, floats[floats_cnt][4],
6184 UnicodeToLocal(ldomXPointer(node, 0).toString()).c_str(),
6185 UnicodeToLocal(ldomXPointer(fbox, 0).toString()).c_str());
6186 }
6187 */
6188 floats_cnt++;
6189 }
6190 }
6191
generateEmbeddedFloatsFromFootprints(int final_width)6192 void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width )
6193 {
6194 floats_cnt = 0;
6195 // Add fake floats (to represent real outer floats) so that
6196 // their rectangles are considered when laying out lines
6197 // and other floats.
6198 // We need to keep them even if left_w or right_w <=0 (in
6199 // which case they'll have no visual impact on the text),
6200 // just so we can clear them when a <BR style="clear:">
6201 // is met.
6202 // Note: we give inward_margin=0 with fake floats (we
6203 // could compute them, but we would need 2 other slots
6204 // in RenderRectAccessor to store them, so let's not).
6205 // Top left rectangle
6206 if ( left_h > 0 ) {
6207 floats[floats_cnt][0] = 0; // x
6208 floats[floats_cnt][1] = 0; // y
6209 floats[floats_cnt][2] = left_w; // width
6210 floats[floats_cnt][3] = left_h; // height
6211 floats[floats_cnt][4] = 0; // is_right
6212 floats[floats_cnt][5] = 0; // inward_margin
6213 floats_cnt++;
6214 }
6215 // Top right rectangle
6216 if ( right_h > 0 ) {
6217 floats[floats_cnt][0] = final_width - right_w; // x
6218 floats[floats_cnt][1] = 0; // y
6219 floats[floats_cnt][2] = right_w; // width
6220 floats[floats_cnt][3] = right_h; // height
6221 floats[floats_cnt][4] = 1; // is_right
6222 floats[floats_cnt][5] = 0; // inward_margin
6223 floats_cnt++;
6224 }
6225 // Dummy 0x0 float for minimal y for next left float
6226 if ( left_min_y > 0 ) {
6227 floats[floats_cnt][0] = 0; // x
6228 floats[floats_cnt][1] = left_min_y; // y
6229 floats[floats_cnt][2] = 0; // width
6230 floats[floats_cnt][3] = 0; // height
6231 floats[floats_cnt][4] = 0; // is_right
6232 floats[floats_cnt][5] = 0; // inward_margin
6233 floats_cnt++;
6234 }
6235 // Dummy 0x0 float for minimal y for next right float
6236 if ( right_min_y > 0 ) {
6237 floats[floats_cnt][0] = final_width; // x
6238 floats[floats_cnt][1] = right_min_y; // y
6239 floats[floats_cnt][2] = 0; // width
6240 floats[floats_cnt][3] = 0; // height
6241 floats[floats_cnt][4] = 1; // is_right
6242 floats[floats_cnt][5] = 0; // inward_margin
6243 floats_cnt++;
6244 }
6245 }
6246
store(ldomNode * node)6247 void BlockFloatFootprint::store(ldomNode * node)
6248 {
6249 RenderRectAccessor fmt( node );
6250 if ( use_floatIds ) {
6251 RENDER_RECT_SET_FLAG(fmt, FINAL_FOOTPRINT_AS_SAVED_FLOAT_IDS);
6252 fmt.setInvolvedFloatIds( nb_floatIds, floatIds );
6253 }
6254 else {
6255 RENDER_RECT_UNSET_FLAG(fmt, FINAL_FOOTPRINT_AS_SAVED_FLOAT_IDS);
6256 fmt.setTopRectsExcluded( left_w, left_h, right_w, right_h );
6257 fmt.setNextFloatMinYs( left_min_y, right_min_y );
6258 }
6259 if ( no_clear_own_floats ) {
6260 RENDER_RECT_SET_FLAG(fmt, NO_CLEAR_OWN_FLOATS);
6261 }
6262 else {
6263 RENDER_RECT_UNSET_FLAG(fmt, NO_CLEAR_OWN_FLOATS);
6264 }
6265 fmt.push();
6266 }
6267
restore(ldomNode * node,int final_width)6268 void BlockFloatFootprint::restore(ldomNode * node, int final_width)
6269 {
6270 RenderRectAccessor fmt( node );
6271 if ( RENDER_RECT_HAS_FLAG(fmt, FINAL_FOOTPRINT_AS_SAVED_FLOAT_IDS) ) {
6272 use_floatIds = true;
6273 fmt.getInvolvedFloatIds( nb_floatIds, floatIds );
6274 generateEmbeddedFloatsFromFloatIds( node, final_width );
6275 }
6276 else {
6277 fmt.getTopRectsExcluded( left_w, left_h, right_w, right_h );
6278 fmt.getNextFloatMinYs( left_min_y, right_min_y );
6279 generateEmbeddedFloatsFromFootprints( final_width );
6280 }
6281 no_clear_own_floats = RENDER_RECT_HAS_FLAG(fmt, NO_CLEAR_OWN_FLOATS);
6282 }
6283
getTopShiftX(int final_width,bool get_right_shift)6284 int BlockFloatFootprint::getTopShiftX(int final_width, bool get_right_shift)
6285 {
6286 int shift_x = 0;
6287 for (int i=0; i<floats_cnt; i++) {
6288 int * flt = floats[i];
6289 if ( flt[1] <= 0 && flt[3] > 0 ) { // Float running at y=0 with some height
6290 if ( !get_right_shift && !flt[4] ) { // Left float and left shift requested
6291 int flt_right = flt[0] + flt[2]; // x + width
6292 if ( flt_right > shift_x ) {
6293 shift_x = flt_right;
6294 }
6295 }
6296 else if ( get_right_shift && flt[4] ) { // Right float and right shift requested
6297 int flt_left = flt[0] - final_width; // x - final_width (negative value relative to right border)
6298 if ( flt_left < shift_x ) {
6299 shift_x = flt_left;
6300 }
6301 }
6302 }
6303 }
6304 return shift_x;
6305 }
6306
6307 // Enhanced block rendering
renderBlockElementEnhanced(FlowState * flow,ldomNode * enode,int x,int container_width,lUInt32 flags)6308 void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int container_width, lUInt32 flags )
6309 {
6310 if (!enode)
6311 return;
6312 if ( ! enode->isElement() ) {
6313 crFatalError(111, "Attempting to render Text node");
6314 }
6315
6316 int m = enode->getRendMethod();
6317 if (m == erm_invisible) // don't render invisible blocks
6318 return;
6319
6320 css_style_ref_t style = enode->getStyle();
6321 lUInt16 nodeElementId = enode->getNodeId();
6322
6323 // force_pb will force a page break before and after the fragment
6324 // this is necessary for non-linear fragments if we want the
6325 // possibility of hiding them from the normal paging flow
6326 bool force_pb = false;
6327 if ( nodeElementId == el_DocFragment) {
6328 if ( enode->hasAttribute( attr_NonLinear ) ) {
6329 flow->newSequence(true);
6330 force_pb = enode->getDocument()->getDocFlag(DOC_FLAG_NONLINEAR_PAGEBREAK);
6331 }
6332 else {
6333 flow->newSequence(false);
6334 }
6335 }
6336 else if ( nodeElementId == el_body ) {
6337 // We also set this attribute on 2nd++ BODYs in FB2 documents
6338 // (as this attribute is only set on FB2 documents, and all
6339 // 2nd++ BODYs get it, no need to check for the doc format and
6340 // no need to reset flow sequence when BODY without met.)
6341 if ( enode->hasAttribute( attr_NonLinear ) ) {
6342 flow->newSequence(true);
6343 force_pb = enode->getDocument()->getDocFlag(DOC_FLAG_NONLINEAR_PAGEBREAK);
6344 }
6345 }
6346
6347 // See if dir= attribute or CSS specified direction
6348 int direction = flow->getDirection();
6349 if ( enode->hasAttribute( attr_dir ) ) {
6350 lString32 dir = enode->getAttributeValue( attr_dir );
6351 dir = dir.lowercase(); // (no need for trim(), it's done by the XMLParser)
6352 if ( dir.compare("rtl") == 0 ) {
6353 direction = REND_DIRECTION_RTL;
6354 }
6355 else if ( dir.compare("ltr") == 0 ) {
6356 direction = REND_DIRECTION_LTR;
6357 }
6358 else if ( dir.compare("auto") == 0 ) {
6359 direction = REND_DIRECTION_UNSET; // let fribidi detect direction
6360 }
6361 }
6362 // Allow CSS direction to override the attribute one (content creators are
6363 // advised to use the dir= attribute and not CSS direction - as CSS should
6364 // be less common, we allow tweaking direction via styles).
6365 if ( style->direction != css_dir_inherit ) {
6366 if ( style->direction == css_dir_rtl )
6367 direction = REND_DIRECTION_RTL;
6368 else if ( style->direction == css_dir_ltr )
6369 direction = REND_DIRECTION_LTR;
6370 else if ( style->direction == css_dir_unset )
6371 direction = REND_DIRECTION_UNSET;
6372 }
6373 bool is_rtl = direction == REND_DIRECTION_RTL; // shortcut for followup tests
6374
6375 // See if lang= attribute
6376 bool has_lang_attribute = false;
6377 if ( enode->hasAttribute( attr_lang ) && !enode->getAttributeValue( attr_lang ).empty() ) {
6378 // We'll probably have to check it is a valid lang specification
6379 // before overriding the upper one.
6380 // lString32 lang = enode->getAttributeValue( attr_lang );
6381 // LangManager->check(lang)...
6382 // In here, we don't care about the language, we just need to
6383 // know if this node specifies one, so children final blocks
6384 // can fetch if from it.
6385 has_lang_attribute = true;
6386 }
6387
6388 // See if this block is a footnote container, so we can deal with it accordingly
6389 bool isFootNoteBody = false;
6390 lString32 footnoteId;
6391 // Allow displaying footnote content at the bottom of all pages that contain a link
6392 // to it, when -cr-hint: footnote-inpage is set on the footnote block container.
6393 if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) &&
6394 enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) {
6395 footnoteId = enode->getFirstInnerAttributeValue(attr_id);
6396 if ( !footnoteId.empty() )
6397 isFootNoteBody = true;
6398 // Notes:
6399 // It fails when that block element has itself an id, but links
6400 // do target an other inline sub element id (getFirstInnerAttributeValue()
6401 // would get the block element id, and there would be no existing footnote
6402 // for the link target id).
6403 // Not tested how it would behave with nested "-cr-hint: footnote-inpage"
6404 }
6405 // For fb2 documents. Description of the <body> element from FictionBook2.2.xsd:
6406 // Main content of the book, multiple bodies are used for additional
6407 // information, like footnotes, that do not appear in the main book
6408 // flow. The first body is presented to the reader by default, and
6409 // content in the other bodies should be accessible by hyperlinks. Name
6410 // attribute should describe the meaning of this body, this is optional
6411 // for the main body.
6412 /* Don't do that anymore in this hardcoded / not disable'able way: one can
6413 * enable in-page footnotes in fb2.css or a style tweak by just using:
6414 * body[name="notes"] section { -cr-hint: footnote-inpage; }
6415 * body[name="comments"] section { -cr-hint: footnote-inpage; }
6416 * which will be hanbled by previous check.
6417 *
6418 if ( enode->getNodeId()==el_section && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) {
6419 ldomNode * body = enode->getParentNode();
6420 while ( body != NULL && body->getNodeId()!=el_body )
6421 body = body->getParentNode();
6422 if ( body ) {
6423 if (body->getAttributeValue(attr_name) == "notes" || body->getAttributeValue(attr_name) == "comments")
6424 footnoteId = enode->getAttributeValue(attr_id);
6425 if ( !footnoteId.empty() )
6426 isFootNoteBody = true;
6427 }
6428 }
6429 */
6430
6431 // is this a floating float container (floatBox)?
6432 bool is_floating = BLOCK_RENDERING(flags, FLOAT_FLOATBOXES) && enode->isFloatingBox();
6433 bool is_floatbox_child = BLOCK_RENDERING(flags, FLOAT_FLOATBOXES)
6434 && enode->getParentNode() && enode->getParentNode()->isFloatingBox();
6435 // is this a inline block container (inlineBox)?
6436 bool is_inline_box = enode->isBoxingInlineBox();
6437 bool is_inline_box_child = enode->getParentNode() && enode->getParentNode()->isBoxingInlineBox();
6438
6439 // In the business of computing width and height, we should handle a bogus
6440 // embedded block (<inlineBox T="EmbeddedBlock">) (and its child) just
6441 // like any normal block element (taking the full width of its container
6442 // if no specified width, without the need to get its rendered width).
6443 if ( is_inline_box && enode->isEmbeddedBlockBoxingInlineBox(true) ) {
6444 is_inline_box = false;
6445 }
6446 if ( is_inline_box_child && enode->getParentNode()->isEmbeddedBlockBoxingInlineBox(true) ) {
6447 is_inline_box_child = false;
6448 }
6449
6450 int em = enode->getFont()->getSize();
6451
6452 int border_left = measureBorder(enode, 3);
6453 int border_right = measureBorder(enode, 1);
6454 int padding_left = lengthToPx( style->padding[0], container_width, em ) + border_left + DEBUG_TREE_DRAW;
6455 int padding_right = lengthToPx( style->padding[1], container_width, em ) + border_right + DEBUG_TREE_DRAW;
6456 int padding_top = lengthToPx( style->padding[2], container_width, em ) + measureBorder(enode, 0) + DEBUG_TREE_DRAW;
6457 int padding_bottom = lengthToPx( style->padding[3], container_width, em ) + measureBorder(enode, 2) + DEBUG_TREE_DRAW;
6458
6459 css_length_t css_margin_left = style->margin[0];
6460 css_length_t css_margin_right = style->margin[1];
6461
6462 int margin_left = lengthToPx( css_margin_left, container_width, em ) + DEBUG_TREE_DRAW;
6463 int margin_right = lengthToPx( css_margin_right, container_width, em ) + DEBUG_TREE_DRAW;
6464 int margin_top = lengthToPx( style->margin[2], container_width, em ) + DEBUG_TREE_DRAW;
6465 int margin_bottom = lengthToPx( style->margin[3], container_width, em ) + DEBUG_TREE_DRAW;
6466
6467 if ( ! BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_NEGATIVE_MARGINS) ) {
6468 if (margin_left < 0) margin_left = 0;
6469 if (margin_right < 0) margin_right = 0;
6470 }
6471 if ( ! BLOCK_RENDERING(flags, ALLOW_VERTICAL_NEGATIVE_MARGINS) ) {
6472 if (margin_top < 0) margin_top = 0;
6473 if (margin_bottom < 0) margin_bottom = 0;
6474 }
6475 bool margin_left_auto = css_margin_left.type == css_val_unspecified && css_margin_left.value == css_generic_auto;
6476 bool margin_right_auto = css_margin_right.type == css_val_unspecified && css_margin_right.value == css_generic_auto;
6477
6478 // Adjust box size and position
6479
6480 // <HR> gets its style width, height and margin:auto no matter flags
6481 bool is_hr = nodeElementId == el_hr;
6482 // <EMPTY-LINE> block element with height added for empty lines in txt document
6483 bool is_empty_line_elem = nodeElementId == el_empty_line;
6484 // Note: for a short time, we handled <BR> set with "display:block" here
6485 // just like EMPTY-LINE. Before that, block BRs did not end up being part
6486 // of a final node, and were just a block with no height, so not ensuring
6487 // the vertical blank space they aimed at.
6488 // This caused other issues, and comparing with Firefox/Calibre, it looks
6489 // like it's just best to always force BR to be css_d_inline, which we do
6490 // in setNodeStyle(). So, we'll never meet any <BR> here.
6491
6492 // Get any style height to be ensured below (just before we add bottom
6493 // padding when erm_block or erm_final)
6494 // Otherwise, this block height will just be its rendered content height.
6495 int style_h = -1;
6496 bool apply_style_height = false;
6497 css_length_t style_height;
6498 int style_height_base_em;
6499 if ( is_floating || is_inline_box ) {
6500 // Nothing special to do: the child style height will be
6501 // enforced by subcall to renderBlockElement(child)
6502 }
6503 else if ( is_hr || is_empty_line_elem || BLOCK_RENDERING(flags, ENSURE_STYLE_HEIGHT) ) {
6504 // We always use the style height for <HR>, to actually have
6505 // a height to fill with its color
6506 style_height = style->height;
6507 style_height_base_em = em;
6508 apply_style_height = true;
6509 if ( is_empty_line_elem && style_height.type == css_val_unspecified ) {
6510 // No height specified: default to line-height, just like
6511 // if it were rendered final.
6512 int line_h;
6513 if ( style->line_height.type == css_val_unspecified &&
6514 style->line_height.value == css_generic_normal ) {
6515 line_h = enode->getFont()->getHeight(); // line-height: normal
6516 }
6517 else {
6518 // In all other cases (%, em, unitless/unspecified), we can just
6519 // scale 'em', and use the computed value for absolute sized
6520 // values and 'rem' (related to root element font size).
6521 line_h = lengthToPx(style->line_height, em, em, true);
6522 }
6523 // Scale line_h according to document's _interlineScaleFactor, but
6524 // not if it was already in screen_px, which means it has already
6525 // been scaled (in setNodeStyle() when inherited).
6526 int interline_scale_factor = enode->getDocument()->getInterlineScaleFactor();
6527 if (style->line_height.type != css_val_screen_px && interline_scale_factor != INTERLINE_SCALE_FACTOR_NO_SCALE)
6528 line_h = (line_h * interline_scale_factor) >> INTERLINE_SCALE_FACTOR_SHIFT;
6529 style_height.value = line_h;
6530 style_height.type = css_val_screen_px;
6531 }
6532 }
6533 if ( apply_style_height && style_height.type != css_val_unspecified ) {
6534 if ( !BLOCK_RENDERING(flags, ALLOW_STYLE_W_H_ABSOLUTE_UNITS) &&
6535 style_height.type != css_val_percent && style_height.type != css_val_em &&
6536 style_height.type != css_val_ex && style_height.type != css_val_rem ) {
6537 apply_style_height = false;
6538 }
6539 if ( is_hr || is_empty_line_elem || apply_style_height ) {
6540 style_h = lengthToPx( style_height, container_width, style_height_base_em );
6541 if ( BLOCK_RENDERING(flags, USE_W3C_BOX_MODEL) ) {
6542 // If W3C box model requested, CSS height specifies the height
6543 // of the content box, so we just add paddings and borders
6544 // to the height we got from styles (paddings will be removed
6545 // when enforcing it below, but we keep the computation
6546 // common to both models doing it that way).
6547 style_h += padding_top + padding_bottom;
6548 }
6549 }
6550 }
6551
6552 // Compute this block width
6553 int width;
6554 bool auto_width = false;
6555 bool table_shrink_to_fit = false;
6556
6557 if ( is_floating || is_inline_box ) {
6558 // Floats width computation - which should also work as-is for inline block box
6559 // We need to have a width for floats, so we don't ignore anything no
6560 // matter the flags.
6561 // As the el_floatBox itself does not have any style->width or margins,
6562 // we should compute our width from the child style, and possibly
6563 // from its rendered content width.
6564 ldomNode * child = enode->getChildNode(0);
6565 int child_em = child->getFont()->getSize();
6566 css_style_ref_t child_style = child->getStyle();
6567
6568 // We may tweak child styles
6569 bool style_changed = false;
6570 // If the child paddings are in %, they are related to this container width!
6571 // As we won't have access to it anymore when rendering the float,
6572 // convert them to screen_px now.
6573 css_style_ref_t newstyle(new css_style_rec_t);
6574 for (int i=0; i<4; i++) {
6575 if ( child_style->padding[i].type == css_val_percent ) {
6576 if (!style_changed) {
6577 copystyle(child_style, newstyle);
6578 style_changed = true;
6579 }
6580 newstyle->padding[i].type = css_val_screen_px;
6581 newstyle->padding[i].value = lengthToPx( child_style->padding[i], container_width, child_em );
6582 }
6583 }
6584 // Same for width, as getRenderedWidths() won't ensure width in %
6585 if ( child_style->width.type == css_val_percent ) {
6586 if (!style_changed) {
6587 copystyle(child_style, newstyle);
6588 style_changed = true;
6589 }
6590 newstyle->width.type = css_val_screen_px;
6591 newstyle->width.value = lengthToPx( child_style->width, container_width, child_em );
6592 }
6593 // (We could do the same fot height if in %, but it looks like Firefox
6594 // just ignore floats height in %, so let's ignore them too.)
6595 if ( ! BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_BLOCK_OVERFLOW) ) {
6596 // Simplest way to avoid a float overflowing its floatBox is to
6597 // ensure no negative margins
6598 int child_margin_left = lengthToPx( child_style->margin[0], container_width, child_em );
6599 int child_margin_right = lengthToPx( child_style->margin[1], container_width, child_em );
6600 if ( child_margin_left < 0 ) {
6601 if (!style_changed) {
6602 copystyle(child_style, newstyle);
6603 style_changed = true;
6604 }
6605 newstyle->margin[0].type = css_val_screen_px;
6606 newstyle->margin[0].value = 0;
6607 }
6608 if ( child_margin_right < 0 ) {
6609 if (!style_changed) {
6610 copystyle(child_style, newstyle);
6611 style_changed = true;
6612 }
6613 newstyle->margin[1].type = css_val_screen_px;
6614 newstyle->margin[1].value = 0;
6615 }
6616 }
6617 // Save child styles if we updated them
6618 if ( style_changed ) {
6619 child->setStyle(newstyle);
6620 child_style = newstyle;
6621 }
6622
6623 // The margins of the element with float: are related to our container_width,
6624 // so account for them here, and don't let getRenderedWidths() add them (or they
6625 // would be reverse-computed from the inner content)
6626 int child_margin_left = lengthToPx( child_style->margin[0], container_width, child_em );
6627 int child_margin_right = lengthToPx( child_style->margin[1], container_width, child_em );
6628 int child_margins = child_margin_left + child_margin_right;
6629 // A floatBox does not have any margin/padding/border itself, but we
6630 // may add some border with CSS for debugging, so account for any
6631 int floatBox_paddings = padding_left + padding_right;
6632 // We let getRenderedWidths() give us the width of the float content:
6633 // if the float element itself has a style->width, we'll get it, with
6634 // or without paddings depending on USE_W3C_BOX_MODEL).
6635 int max_content_width = 0;
6636 int min_content_width = 0;
6637 // If the floating child does not have a width, inner elements may have one
6638 // Even if main flow is not ensuring style width, we need to ensure it
6639 // for floats to avoid getting page wide floats which won't look like
6640 // they are floating.
6641 int rend_flags = flags | BLOCK_RENDERING_ENSURE_STYLE_WIDTH | BLOCK_RENDERING_ALLOW_STYLE_W_H_ABSOLUTE_UNITS;
6642 // (ignoreMargin=true to ignore the child node margins as we already have them)
6643 getRenderedWidths(child, max_content_width, min_content_width, direction, true, rend_flags);
6644 // We should not exceed our container_width
6645 if (max_content_width + child_margins + floatBox_paddings < container_width) {
6646 width = max_content_width + child_margins + floatBox_paddings;
6647 }
6648 else {
6649 // It looks like Firefox never use min_content_width.
6650 // If max_content_width does not fit, we don't go as small as
6651 // the longest word length: we take the full container_width.
6652 width = container_width;
6653 }
6654 auto_width = true; // no more width tweaks (nor any x adjustment if is_rtl)
6655 // printf("floatBox width: max_w=%d min_w=%d => %d", max_content_width, min_content_width, width);
6656 }
6657 else if ( is_floatbox_child || is_inline_box_child ) {
6658 // The float style or rendered width has been applied to the wrapping
6659 // floatBox, so just remove node's margins of the container (the
6660 // floatBox) to get the child width.
6661 width = container_width - margin_left - margin_right;
6662 auto_width = true; // no more width tweaks
6663 // For tables, keep table_shrink_to_fit=false, so this width is not reduced
6664 }
6665 else { // regular element (non-float)
6666 bool apply_style_width = false;
6667 css_length_t style_width = style->width;
6668 if ( style->display > css_d_table ) {
6669 // table sub-elements widths are managed by the table layout algorithm
6670 apply_style_width = false;
6671 }
6672 else {
6673 // Only if ENSURE_STYLE_WIDTH as we may prefer having
6674 // full width text blocks to not waste reading width with blank areas.
6675 if ( style_width.type != css_val_unspecified ) {
6676 apply_style_width = BLOCK_RENDERING(flags, ENSURE_STYLE_WIDTH);
6677 if ( apply_style_width && !BLOCK_RENDERING(flags, ALLOW_STYLE_W_H_ABSOLUTE_UNITS) &&
6678 style_width.type != css_val_percent && style_width.type != css_val_em &&
6679 style_width.type != css_val_ex && style_width.type != css_val_rem ) {
6680 apply_style_width = false;
6681 }
6682 if ( is_hr ) {
6683 // We always use style width for <HR> for aesthetic reasons
6684 apply_style_width = true;
6685 }
6686 }
6687 else if ( style->display == css_d_table || m == erm_table ) {
6688 // Table with no style width can shrink (and so can inline-table
6689 // and anonymous incomplete-but-completed tables).
6690 // If we are not ensuring style widths above, tables with
6691 // a width will not shrink and will fit container width.
6692 // (This should allow our table style tweaks to work
6693 // when !ENSURE_STYLE_WIDTH.)
6694 table_shrink_to_fit = true;
6695 }
6696 }
6697 if ( apply_style_width ) {
6698 width = lengthToPx( style_width, container_width, em );
6699 // In all crengine computation, width/fmt.getWidth() is the width
6700 // of the border box (content box + paddings + borders).
6701 // If we use what we got directly, we are in the traditional
6702 // box model (Netscape / IE5 / crengine legacy/default).
6703 if ( style->display == css_d_table ) {
6704 // TABLE style width always specifies its border box.
6705 // It's an exception to the W3C box model, as witnessed
6706 // with Firefox, and discussed at:
6707 // https://stackoverflow.com/questions/19068909/why-is-box-sizing-acting-different-on-table-vs-div
6708 }
6709 else if ( BLOCK_RENDERING(flags, USE_W3C_BOX_MODEL) ) {
6710 // If W3C box model requested, CSS width specifies the width
6711 // of the content box.
6712 // In crengine, the width we deal with is the border box, so we
6713 // just add paddings and borders to the width we got from styles.
6714 width += padding_left + padding_right;
6715 }
6716 // printf(" apply_style_width => %d\n", width);
6717 }
6718 else {
6719 width = container_width - margin_left - margin_right;
6720 auto_width = true; // no more width tweaks
6721 }
6722 }
6723
6724 // What about a width with a negative value?
6725 // It seems we are fine with a negative width in our recursive
6726 // x and width computations, as children may make it positive
6727 // again with negative margins, and we seem to always end up
6728 // with a correct x position to draw our block at, quite
6729 // very similarly as Firefox.
6730 // So, our x/dx computations below seem like they may need
6731 // us to keep a negative width value to be done right.
6732 // (We'll revisit that when we meet counterexamples.)
6733 //
6734 // There are nevertheless a few things to take care of later
6735 // when we get a negative width:
6736 // - We want to avoid negative RenderRectAcessor.getWidth(),
6737 // so we'll set it to zero. It's mostly only used to draw
6738 // borders and backgrounds, and it seems alright to have
6739 // zero of these on such blocks.
6740 // Caveat: elementFromPoint()/createXPointer may get
6741 // confused with such rect, and not travel thru them, or
6742 // skip them.
6743 // - A table with a zero or negative width (which can happen
6744 // with very busy imbricated tables) won't be drawn, and
6745 // it's rendering method will be switched to erm_killed
6746 // to display some small visual indicator.
6747 // - Legacy rendering code keeps a negative width in
6748 // RenderRectAccessor, and, with erm_final nodes, provides
6749 // it as is to node->renderFinalBlock(), which casts it to
6750 // a signed integer when calling txform->Format(inner_width),
6751 // resulting in text formatted over a huge overflowing width.
6752 // This is somehow a quite good end solution to deal with
6753 // text formatting over a bogus negative width (instead of
6754 // just not displaying anything) as it gives a hint to the
6755 // user that something is wrong, with this text overflowing
6756 // the screen.
6757 // So, we won't be using erm_killed for these erm_final nodes,
6758 // but we will set the inner_width to be 1x screen width. Some
6759 // text may still overflow, text selection may not work, but
6760 // a bit more of it will be seen on multiple lines.
6761 // Note: Firefox in this case uses the min content width as
6762 // a fallback width (we could do the same, but it is costly
6763 // and may result in adding many many pages with a narrow
6764 // column of one or two words on each line.
6765
6766 // Reference: https://www.w3.org/TR/CSS2/visudet.html#blockwidth
6767 // As crengine uses internally the traditional box model, the width
6768 // we are computing here is the width between the borders of the box,
6769 // including padding and border.
6770 // So, these rules are simplified to:
6771 // - margin_left + width + margin_right = container_width
6772 // - If width is not 'auto', and 'margin_left + width + margin_right' is larger
6773 // than container_width, then any 'auto' values for 'margin-left' or 'margin-right'
6774 // are, for the following rules, treated as zero.
6775 // - If all of the above have a computed value other than 'auto', the values are
6776 // said to be "over-constrained" and one of the used values will have to be
6777 // different from its computed value. If the 'direction' property of the
6778 // containing block has the value 'ltr', the specified value of 'margin-right'
6779 // is ignored and the value is calculated so as to make the equality true.
6780 // - If there is exactly one value specified as 'auto', its used value follows
6781 // from the equality
6782 // - If 'width' is set to 'auto', any other 'auto' values become '0' and 'width'
6783 // follows from the resulting equality
6784 // - If both 'margin-left' and 'margin-right' are 'auto', their used values are
6785 // equal. This horizontally centers the element with respect to the edges
6786 // of the containing block
6787
6788 // We now have the prefered width, and we need to adjust x to the position
6789 // where this width is to start.
6790 // ('x' might not be 0, as it includes the parent padding & borders)
6791 // (margin_left and margin_right have a value of 0 if they are "auto")
6792
6793 // In most cases, our shift from x (our margin left inside container_width)
6794 // is... margin_left
6795 int dx = margin_left;
6796
6797 // Strangely, when floats are involved, a HR behaves differently than
6798 // a regular DIV (observed with Firefox): a DIV is sized as if there
6799 // was no float, and only its text will adjust to be in-between floats,
6800 // while a HR box does adjust to fit between the floats. Couldn't find
6801 // any mention of that in the CSS specs...
6802 // Let's try to handle that, even if it feels hackish and might not be right.
6803 int adjusted_container_width = container_width;
6804 int adjusted_forced_x_shift = 0;
6805 if ( is_hr && flow->hasActiveFloats() ) {
6806 // <HR> should not be drawn over floats (except if negative
6807 // margins or larger specified width - its width in % is to
6808 // stay computed as a % of its container width)
6809 int dx_left;
6810 int dx_right;
6811 flow->getFloatsCurrentShifts(dx_left, dx_right);
6812 if ( dx_right > 0 ) {
6813 adjusted_container_width -= dx_right;
6814 }
6815 if ( dx_left > 0 ) {
6816 adjusted_container_width -= dx_left;
6817 adjusted_forced_x_shift = dx_left;
6818 }
6819 if ( style->width.type == css_val_unspecified ) {
6820 // When no specified width, it is to become the constrained width
6821 width = adjusted_container_width;
6822 }
6823 // And go again at adjusting this HR position
6824 auto_width = false;
6825 }
6826 if ( !auto_width ) { // We have a width that may not fill all available space
6827 // printf("fixed width: %d\n", width);
6828 // For these initial overflow checks, we use the original container_width
6829 // and not the adjusted one
6830 if ( width + margin_left + margin_right > container_width ) {
6831 if ( is_rtl ) {
6832 margin_left = 0; // drop margin_left if RTL
6833 dx = 0;
6834 }
6835 else {
6836 margin_right = 0; // drop margin_right otherwise
6837 }
6838 }
6839 if ( width + margin_left + margin_right > container_width ) {
6840 // We can't ensure any auto (or should we? ensure centering
6841 // by even overflow on each side?)
6842 }
6843 else { // We fit into container_width
6844 bool margin_auto_ensured = false;
6845 if ( BLOCK_RENDERING(flags, ENSURE_MARGIN_AUTO_ALIGNMENT) || is_hr ) {
6846 // https://www.hongkiat.com/blog/css-margin-auto/
6847 // "what do you think will happen when the value auto is given
6848 // to only one of those? A left or right margin with auto will
6849 // take up all of the "available" space making the element
6850 // look like it has been flushed right or left."
6851 // The CSS specs do not seem to mention that.
6852 // But Firefox ensures it. So let's do the same.
6853 //
6854 // (if margin_left_auto, we have until now: dx = margin_left = 0)
6855 if ( margin_left_auto && margin_right_auto ) {
6856 dx = (adjusted_container_width - width) / 2;
6857 margin_auto_ensured = true;
6858 }
6859 else if ( margin_left_auto ) {
6860 dx = adjusted_container_width - width - margin_right;
6861 margin_auto_ensured = true;
6862 }
6863 else if ( margin_right_auto ) {
6864 // No dx tweak needed
6865 margin_auto_ensured = true;
6866 }
6867 if ( is_hr && dx < 0 && margin_auto_ensured ) {
6868 // With Firefox, when any margin is auto and the HR width
6869 // doesn't fit, it is fitted left.
6870 dx = 0;
6871 }
6872 }
6873 if ( !margin_auto_ensured ) {
6874 // Nothing else needed for LTR: stay stuck to the left
6875 // For RTL: stick it to the right
6876 if (is_rtl) {
6877 dx = adjusted_container_width - width;
6878 }
6879 }
6880 }
6881 // Add left shift imposed by left floats to HR
6882 dx += adjusted_forced_x_shift;
6883 }
6884
6885 // Prevent overflows if not allowed
6886 if ( ! BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_BLOCK_OVERFLOW) ) {
6887 if ( width > container_width ) { // width alone is bigger than container
6888 dx = 0; // drop any left shift
6889 width = container_width; // adjust to contained width
6890 }
6891 else if ( dx + width > container_width ) {
6892 // width is smaller that container's, but dx makes it overflow
6893 dx = container_width - width;
6894 }
6895 }
6896 if ( ! BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_PAGE_OVERFLOW) ) {
6897 // Ensure it does not go past the left or right of the original
6898 // container width (= page, for main flow)
6899 // Note: not (yet) implemented for floats (but it is naturally
6900 // ensured if !ALLOW_HORIZONTAL_BLOCK_OVERFLOW)
6901 int o_width = flow->getOriginalContainerWidth();
6902 int abs_x = flow->getCurrentAbsoluteX();
6903 // abs_x already accounts for x (=padding_left of parent container,
6904 // which is given to flow->newBlockLevel() before being also given
6905 // to renderBlockElementEnhanced() as x).
6906 if ( abs_x + dx < 0 ) {
6907 dx = - abs_x; // clip to page left margin
6908 }
6909 if ( abs_x + dx + width > o_width ) {
6910 width = o_width - abs_x - dx; // clip width to page right margin
6911 }
6912 }
6913
6914 // Apply dx, and we're done with width and x
6915 x += dx;
6916 // printf("width: %d dx: %d > x: %d\n", width, dx, x);
6917
6918 bool no_margin_collapse = false;
6919 if ( flow->getCurrentLevel() == 0 ) {
6920 // "Margins of the root element's box do not collapse"
6921 // We'll push it immediately below
6922 no_margin_collapse = true;
6923 }
6924 else if ( flow->getCurrentLevel() == 1 && (enode->getParentNode()->isFloatingBox() ||
6925 enode->getParentNode()->isBoxingInlineBox()) ) {
6926 // The inner margin of the real float element (the single child of a floatBox)
6927 // have to be pushed and not collapse with outer margins so they can
6928 // get accounted in the float height.
6929 // (This must be true also with inline-block boxes, but not tested/verified.)
6930 no_margin_collapse = true;
6931 }
6932
6933 // Ensure page breaks following the rules from:
6934 // https://www.w3.org/TR/CSS2/page.html#allowed-page-breaks
6935 // Also ensure vertical margin collapsing, with rules from:
6936 // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
6937 // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing
6938 // (Test suite for margin collapsing: http://test.weasyprint.org/suite-css21/chapter8/section3/)
6939 // All of this is mostly ensured in flow->pushVerticalMargin()
6940 int break_before = CssPageBreak2Flags( style->page_break_before );
6941 int break_after = CssPageBreak2Flags( style->page_break_after );
6942 int break_inside = CssPageBreak2Flags( style->page_break_inside );
6943 // Note: some test suites seem to indicate that an inner "break-inside: auto"
6944 // can override an outer "break-inside: avoid". We don't ensure that.
6945
6946 // enforce page breaks if needed
6947 if (force_pb) {
6948 break_before = RN_SPLIT_ALWAYS;
6949 break_after = RN_SPLIT_ALWAYS;
6950 }
6951
6952 if ( no_margin_collapse ) {
6953 // Push any earlier margin so it does not get collapsed with this one
6954 flow->pushVerticalMargin();
6955 }
6956 // We don't shift y yet. We accumulate margin, and, after ensuring
6957 // collapsing, emit it on the first non-margin real content added.
6958 flow->addVerticalMargin( enode, margin_top, break_before, true ); // is_top_margin=true
6959
6960 LFormattedTextRef txform;
6961
6962 // Set what we know till now about this block
6963 RenderRectAccessor fmt( enode );
6964 // Set direction for all blocks (needed for text in erm_final, but also for list item
6965 // markers in erm_block, so that DrawDocument can draw it on the right if rtl).
6966 RENDER_RECT_SET_DIRECTION(fmt, direction);
6967 // Store lang node index if it's an erm_final node (it's only needed for these,
6968 // as the starting lang for renderFinalBlock())
6969 if ( m == erm_final ) {
6970 if ( has_lang_attribute )
6971 fmt.setLangNodeIndex( enode->getDataIndex() );
6972 else
6973 fmt.setLangNodeIndex( flow->getLangNodeIndex() );
6974 }
6975 fmt.setX( x );
6976 fmt.setY( flow->getCurrentRelativeY() );
6977 fmt.setWidth( width );
6978 if ( width < 0) {
6979 // We might be fine with a negative width when recursively rendering
6980 // children nodes (which may make it positive again with negative
6981 // margins). But we don't want our RenderRect to have a negative width.
6982 fmt.setWidth( 0 );
6983 }
6984 fmt.setHeight( 0 ); // will be updated below
6985 fmt.push();
6986 // This has to be delayed till now: it may adjust the Y we just setY() above
6987 if ( no_margin_collapse ) {
6988 flow->pushVerticalMargin();
6989 }
6990
6991 #ifdef DEBUG_BLOCK_RENDERING
6992 if (!flow->isMainFlow()) printf("\t\t|");
6993 for (int i=1; i<=flow->getCurrentLevel(); i++) { printf("%c", flow->isMainFlow() ? ' ':'-'); }
6994 printf("%c", m==erm_block?'B':m==erm_table?'T':m==erm_final?'F':'?');
6995 printf("\t%s", UnicodeToLocal(ldomXPointer(enode, 0).toString()).c_str());
6996 printf("\tc_y=%d (rely=%d)\n", flow->getCurrentAbsoluteY(), flow->getCurrentRelativeY());
6997 #endif
6998
6999 if (width <= 0) {
7000 // In case we get a negative width (no room to render and draw anything),
7001 // which may happen in hyper constrained layouts like heavy nested tables,
7002 // don't go further in the rendering code.
7003 // It seems erm_block nodes do "survive" such negative width,
7004 // by just keeping substracting margin and padding to this negative
7005 // number until we reach an erm_final. For these, below, we possibly
7006 // do our best below to ensure a final positive inner_width.
7007 // So, we only do the following for tables, where the rendering code
7008 // is more easily messed up by negative widths. As we won't show
7009 // any table, and we want the user to notice something is missing,
7010 // we set this element rendering method to erm_killed, and
7011 // DrawDocument will then render a small figure...
7012 if ( m >= erm_table && !(is_floatbox_child || is_inline_box_child) ) {
7013 // (Avoid this with float or inline tables, that have been measured
7014 // and can have a 0-width when they have no content.)
7015 printf("CRE WARNING: no width to draw %s\n", UnicodeToLocal(ldomXPointer(enode, 0).toString()).c_str());
7016 enode->setRendMethod( erm_killed );
7017 fmt.setHeight( 15 ); // not squared, so it does not look
7018 fmt.setWidth( 10 ); // like a list square bullet
7019 fmt.setX( fmt.getX() - 5 );
7020 // We shift it half to the left, so a bit of it can be
7021 // seen if some element on the right covers it with some
7022 // background color.
7023 flow->addContentLine( fmt.getHeight(), RN_SPLIT_BOTH_AVOID, fmt.getHeight() );
7024 return;
7025 }
7026 }
7027
7028 switch( m ) {
7029 case erm_table:
7030 {
7031 // As we don't support laying tables aside floats, just clear
7032 // all floats and push all margins
7033 flow->clearFloats( css_c_both );
7034 flow->pushVerticalMargin();
7035
7036 // We need to update the RenderRectAccessor() as renderTable will
7037 // use it to get the absolute table start y for context.AddLine()'ing
7038 fmt.setY( flow->getCurrentRelativeY() );
7039 fmt.push();
7040
7041 if ( isFootNoteBody )
7042 flow->getPageContext()->enterFootNote( footnoteId );
7043
7044 // Ensure page-break-inside avoid, from the table's style or
7045 // from outer containers
7046 bool avoid_pb_inside = break_inside==RN_SPLIT_AVOID;
7047 if (!avoid_pb_inside)
7048 avoid_pb_inside = flow->getAvoidPbInside();
7049
7050 // We allow a table to shrink width (cells to shrink to their content),
7051 // unless they have a width specified.
7052 // This can be tweaked with:
7053 // table {width: 100% !important} to have tables take the full available width
7054 // table {width: auto !important} to have tables shrink to their content
7055 int table_width = width;
7056 int fitted_width = -1;
7057 bool is_ruby_table = false;
7058 if ( enode->getParentNode()->isBoxingInlineBox() && enode->getParentNode()->getParentNode()
7059 && enode->getParentNode()->getParentNode()->getStyle()->display == css_d_ruby ) {
7060 is_ruby_table = true;
7061 }
7062 // renderTable has not been updated to use 'flow', and it looks
7063 // like it does not really need to.
7064 int h = renderTable( *(flow->getPageContext()), enode, 0, flow->getCurrentRelativeY(),
7065 table_width, table_shrink_to_fit, fitted_width, direction, avoid_pb_inside, true, is_ruby_table );
7066 // Reload fmt, as renderTable() may have set some flags
7067 fmt = RenderRectAccessor( enode );
7068 // (It feels like we don't need to ensure a table specified height.)
7069 fmt.setHeight( h );
7070 // Update table width if it was fitted/shrunk
7071 if (table_shrink_to_fit && fitted_width > 0)
7072 table_width = fitted_width;
7073 fmt.setWidth( table_width );
7074 if (table_width < width) {
7075 // This was already done above, but it needs to be adjusted
7076 // again if the table width was shrunk.
7077 // See for margin: auto, to center or align right the table
7078 int shift_x = 0;
7079 if (is_rtl) { // right align
7080 shift_x = (width - table_width);
7081 }
7082 if (margin_left_auto) {
7083 if (margin_right_auto) { // center align
7084 shift_x = (width - table_width)/2;
7085 }
7086 else { // right align
7087 shift_x = (width - table_width);
7088 }
7089 }
7090 if (shift_x) {
7091 fmt.setX( fmt.getX() + shift_x );
7092 }
7093 }
7094 fmt.push();
7095
7096 flow->moveDown(h);
7097
7098 if ( isFootNoteBody )
7099 flow->getPageContext()->leaveFootNote();
7100
7101 flow->addVerticalMargin( enode, margin_bottom, break_after );
7102 return;
7103 }
7104 break;
7105 case erm_block:
7106 case erm_inline: // For inlineBox elements only
7107 {
7108 if (m == erm_inline && nodeElementId != el_inlineBox) {
7109 printf("CRE WARNING: node discarded (unexpected erm_inline for elem %s)\n",
7110 UnicodeToLocal(ldomXPointer(enode, 0).toString()).c_str());
7111 // (add %s and enode->getText8().c_str() to see text content)
7112 // Might be too early after introducing inline-block support to crash:
7113 // let's just output this warning and ignore the node content.
7114 // crFatalError(143, "erm_inline for element not inlineBox");
7115 return;
7116 }
7117 // Deal with list item marker
7118 // List item marker rendering when css_d_list_item_block
7119 int list_marker_padding = 0; // set to non-zero when list-style-position = outside
7120 int list_marker_height = 0;
7121 if ( style->display == css_d_list_item_block ) {
7122 // list_item_block rendered as block (containing text and block elements)
7123 // Get marker width and height
7124 LFormattedTextRef txform( enode->getDocument()->createFormattedText() );
7125 int list_marker_width;
7126 lString32 marker = renderListItemMarker( enode, list_marker_width, txform.get(), -1, 0);
7127 list_marker_height = txform->Format( (lUInt16)(width - list_marker_width), (lUInt16)enode->getDocument()->getPageHeight(), direction );
7128 if ( ! renderAsListStylePositionInside(style, is_rtl) ) {
7129 // When list_style_position = outside, we have to shift the whole block
7130 // to the right and reduce the available width, which is done
7131 // below when calling renderBlockElement() for each child
7132 list_marker_padding = list_marker_width;
7133 }
7134 else if ( style->list_style_type != css_lst_none ) {
7135 // When list_style_position = inside, we need to let renderFinalBlock()
7136 // know there is a marker to prepend when rendering the first of our
7137 // children (or grand-children, depth first) that is erm_final
7138 // (caveat: the marker will not be shown if any of the first children
7139 // is erm_invisible)
7140 // (No need to do anything when list-style-type none.)
7141 ldomNode * tmpnode = enode;
7142 while ( tmpnode && tmpnode->hasChildren() ) {
7143 tmpnode = tmpnode->getChildNode( 0 );
7144 if (tmpnode && tmpnode->getRendMethod() == erm_final) {
7145 // We need renderFinalBlock() to be able to reach the current
7146 // enode when it will render/draw this tmpnode, so it can call
7147 // renderListItemMarker() on it and get a marker formatted
7148 // according to current node style.
7149 // We store enode's data index into the RenderRectAccessor of
7150 // this erm_final tmpnode so it's saved in the cache.
7151 // (We used to use NodeNumberingProps to store it, but it
7152 // is not saved in the cache.)
7153 RenderRectAccessor tmpfmt( tmpnode );
7154 tmpfmt.setListPropNodeIndex( enode->getDataIndex() );
7155 break;
7156 }
7157 }
7158 }
7159 }
7160
7161 // Note: there's something which can be a bit confusing here:
7162 // we shift the flow state by padding_top (above, while dealing
7163 // with it for page split context) and padding_left (just below).
7164 // But each child x and y (set by renderBlockElement() below) must
7165 // include padding_top and padding_left. So we keep providing these
7166 // to renderBlockElement() even if it feels a bit out of place,
7167 // notably in the float positioning code. But it works...
7168
7169 // Update left and right overflows (usable by glyphs) if this node
7170 // has some background or borders, to be given below to 'flow'.
7171 int usable_overflow_reset_left = -1;
7172 int usable_overflow_reset_right = -1;
7173 if ( style->background_color.type == css_val_color || !style->background_image.empty() ) {
7174 // New (or same) background color specified (we assume there is
7175 // a color change): avoid glyphs/hanging punctuation from leaking
7176 // over the background change.
7177 usable_overflow_reset_left = padding_left;
7178 usable_overflow_reset_right = padding_right;
7179 }
7180 // If there's some border, avoid glyphs/hanging punctuation from
7181 // leaking on or over the border.
7182 if ( border_left ) {
7183 usable_overflow_reset_left = padding_left - border_left;
7184 }
7185 if ( border_right ) {
7186 usable_overflow_reset_right = padding_right - border_right;
7187 }
7188
7189 // Shrink flow state area: children that are float will be
7190 // constrained into this area
7191 // ('width' already had margin_left/_right substracted)
7192 flow->newBlockLevel(width - list_marker_padding - padding_left - padding_right, // width
7193 margin_left + (is_rtl ? 0 : list_marker_padding) + padding_left, // d_left
7194 usable_overflow_reset_left, usable_overflow_reset_right,
7195 break_inside==RN_SPLIT_AVOID,
7196 direction,
7197 has_lang_attribute ? enode->getDataIndex() : -1);
7198
7199 if (padding_top>0) {
7200 // This may push accumulated vertical margin
7201 flow->addContentLine(padding_top, RN_SPLIT_AFTER_AVOID, 0, true);
7202 }
7203
7204 // Enter footnote body only after padding, to get rid of it
7205 // and have lean in-page footnotes
7206 if ( isFootNoteBody ) {
7207 // If no padding were added, add an explicite 0-padding so that
7208 // any accumulated vertical margin is pushed here, and not
7209 // part of the footnote
7210 if (padding_top==0) {
7211 flow->addContentLine(0, RN_SPLIT_AFTER_AVOID, 0, true);
7212 }
7213 flow->getPageContext()->enterFootNote( footnoteId );
7214 }
7215
7216 // recurse all sub-blocks for blocks
7217 int cnt = enode->getChildCount();
7218 for (int i=0; i<cnt; i++) {
7219 ldomNode * child = enode->getChildNode( i );
7220 if (!child)
7221 continue;
7222 if ( child->isText() ) {
7223 // We may occasionally let empty text nodes among block elements,
7224 // just skip them
7225 lString32 s = child->getText();
7226 if ( IsEmptySpace(s.c_str(), s.length() ) )
7227 continue;
7228 crFatalError(144, "Attempting to render non-empty Text node");
7229 }
7230 css_style_ref_t child_style = child->getStyle();
7231
7232 // We must deal differently with children that are floating nodes.
7233 // Different behaviors with "clear:"
7234 // - If a non-floating block has a "clear:", it is moved below the last
7235 // float on that side
7236 // - If a floating block has a "clear:", it is moved below the last
7237 // float on that side BUT the following non-floating blocks should
7238 // not move and continue being rendered at the current y
7239
7240 // todo: if needed, implement float: and clear: inline-start / inline-end
7241
7242 if ( child->isFloatingBox() ) {
7243 // Block floats are positioned respecting the current collapsed
7244 // margin, without actually globally pushing it, and without
7245 // collapsing with it.
7246 int flt_vertical_margin = flow->getCurrentVerticalMargin();
7247 bool is_right = child_style->float_ == css_f_right;
7248 // (style->clear has not been copied to the floatBox: we must
7249 // get it from the floatBox single child)
7250 css_clear_t child_clear = child->getChildNode(0)->getStyle()->clear;
7251 // Provide an empty context so float content does not add lines
7252 // to the page splitting context. The non-floating nodes will,
7253 // and if !DO_NOT_CLEAR_OWN_FLOATS, we'll fill the remaining
7254 // height taken by floats if any.
7255 LVRendPageContext alt_context( NULL, flow->getPageHeight(), false );
7256 // For floats too, the provided x must be the padding-left of the
7257 // parent container of the float (and width must exclude the parent's
7258 // padding-left/right) for the flow to correctly position inner floats
7259 // (but we don't provide padding_top, as if non-zero, we already
7260 // flow->addContentLine() it above, so the flow is already aware of it):
7261 // flow->addFloat() will additionally shift its positioning by the
7262 // child x/y set by this renderBlockElement().
7263 // We provide 0,0 as the usable left/right overflows, so no glyph/hanging
7264 // punctuation will leak outside the floatBox - but the floatBox contains
7265 // the initial float element's margins, which can then be used if it has
7266 // no border (if borders, only the padding can be used).
7267 renderBlockElement( alt_context, child, (is_rtl ? 0 : list_marker_padding) + padding_left,
7268 0, width - list_marker_padding - padding_left - padding_right, 0, 0, direction );
7269 flow->addFloat(child, child_clear, is_right, flt_vertical_margin);
7270 // Gather footnotes links accumulated by alt_context
7271 lString32Collection * link_ids = alt_context.getLinkIds();
7272 if (link_ids->length() > 0) {
7273 for ( int n=0; n<link_ids->length(); n++ ) {
7274 flow->getPageContext()->addLink( link_ids->at(n) );
7275 }
7276 }
7277 }
7278 else {
7279 css_clear_t child_clear = child_style->clear;
7280 // If this child is going to split page, clear all floats before
7281 if ( CssPageBreak2Flags( child_style->page_break_before ) == RN_SPLIT_ALWAYS )
7282 child_clear = css_c_both;
7283 flow->clearFloats( child_clear );
7284 renderBlockElementEnhanced( flow, child, (is_rtl ? 0 : list_marker_padding )+ padding_left,
7285 width - list_marker_padding - padding_left - padding_right, flags );
7286 // Vertical margins collapsing is mostly ensured in flow->pushVerticalMargin()
7287 //
7288 // Various notes about it:
7289 // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing
7290 // "The margins of adjacent siblings are collapsed (except
7291 // when the latter sibling needs to be cleared past floats)."
7292 // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
7293 // "The bottom margin of an in-flow block-level element always
7294 // collapses with the top margin of its next in-flow block-level
7295 // sibling, unless that sibling has clearance. "
7296 // https://www.w3.org/TR/CSS21/visuren.html#clearance
7297 // clearance is not just having clear: it's when clear: has to
7298 // do some moving
7299 // So we should not do this:
7300 // if ( child_clear > css_c_none ) flow->pushVerticalMargin();
7301 // It looks like it just means that the upcoming pushed/collapsed
7302 // vertical margin could be part of the clear'ed vertical area.
7303 // We attempt at doing that in pushVerticalMargin(), and we manage
7304 // to look quite much like how Firefox renders - although we're
7305 // not doing all the computations the specs suggest.
7306 //
7307 // https://www.w3.org/TR/CSS21/box.html#collapsing-margins
7308 // "Adjoining vertical margins collapse, except:
7309 // 2) If the top and bottom margins of an element with clearance
7310 // are adjoining, its margins collapse with the adjoining
7311 // margins of following siblings but that resulting margin does
7312 // not collapse with the bottom margin of the parent block."
7313 // Not sure about this one. Is this about empty elements ("top and
7314 // bottom margins are adjoining" with each other)? Or something else?
7315 }
7316 }
7317
7318 // Ensure there's enough height to fully display the list marker
7319 int current_h = flow->getCurrentRelativeY();
7320 if (list_marker_height && list_marker_height > current_h) {
7321 flow->addContentSpace(list_marker_height - current_h, 1, current_h > 0, true, false);
7322 }
7323
7324 // Leave footnote body before style height and padding, to get
7325 // rid of them and have lean in-page footnotes
7326 if ( isFootNoteBody )
7327 flow->getPageContext()->leaveFootNote();
7328
7329 if (style_h >= 0) {
7330 current_h = flow->getCurrentRelativeY() + padding_bottom;
7331 int pad_h = style_h - current_h;
7332 if (pad_h > 0) {
7333 if (pad_h > flow->getPageHeight()) // don't pad more than one page height
7334 pad_h = flow->getPageHeight();
7335 // Add this space to the page splitting context
7336 // Allow page splitting inside this useless excessive style height
7337 // (Unless it's a <EMPTY-LINE> that we're rather keep it all on a
7338 // page, to avoid text line shifts and ghosting in interline.)
7339 bool split_avoid_inside = is_empty_line_elem;
7340 flow->addContentSpace(pad_h, 1, false, split_avoid_inside, false);
7341 }
7342 }
7343
7344 if ( no_margin_collapse ) {
7345 // Push any earlier margin so it does not get collapsed with this one,
7346 // and we get the resulting margin in the height given by leaveBlockLevel().
7347 flow->pushVerticalMargin();
7348 }
7349 else if ( current_h == 0 && BLOCK_RENDERING(flags, DO_NOT_CLEAR_OWN_FLOATS) && flow->hasActiveFloats() ) {
7350 // There is no problem with some empty height/content blocks
7351 // with vertical margins, unless there are floats around
7352 // (whether they are outer floats, or standalone embedded
7353 // float(s) inside some erm_final block with nothing else,
7354 // which gave that current_h=0).
7355 // If floats are involved, the vertical margin should
7356 // apply above this empty block: it's like the following
7357 // non-empty block should grab these previous empty blocks
7358 // with it, and so the inner floats should be moved down.
7359 // This is complicated to get right, so, to avoid more
7360 // visible glitches and mismatches in floats position and
7361 // footprints, it's safer and less noticable to just push
7362 // vertical margin now and disable any further vertical
7363 // margin until some real content lines have been sent.
7364 // Sample test case:
7365 // <div style="margin: 1em 0">aaa</div>
7366 // <div style="margin: 1em 0"><span style="float: left">bbb</span></div>
7367 // <div style="margin: 1em 0">ccc</div>
7368 // bbb and ccc should be aligned
7369 // So, this is wrong, but the simplest to solve this case:
7370 flow->pushVerticalMargin();
7371 flow->disableVerticalMargin();
7372 // But this drop the H2 top margin in this test case:
7373 // <div>some dummy text</div>
7374 // <div> <!-- just because the float is inner to this div, which will get a 0-height -->
7375 // <div style="float: left">some floating div</div>
7376 // </div>
7377 // <H2>This is a H2</H2>
7378 }
7379
7380 int top_overflow = 0;
7381 int bottom_overflow = 0;
7382 int h = flow->leaveBlockLevel(top_overflow, bottom_overflow);
7383
7384 // padding bottom should be applied after leaveBlockLevel
7385 // (Firefox, with a float taller than text, both in another
7386 // float, applies bottom padding after the inner float)
7387 if (padding_bottom>0) {
7388 int padding_bottom_with_inner_pushed_vm = flow->addContentLine(padding_bottom, RN_SPLIT_BEFORE_AVOID, 0, true);
7389 h += padding_bottom_with_inner_pushed_vm;
7390 bottom_overflow -= padding_bottom_with_inner_pushed_vm;
7391 }
7392
7393 if (h <=0) {
7394 // printf("negative h=%d %d %s %s\n", h, is_floating,
7395 // UnicodeToLocal(ldomXPointer(enode, 0).toString()).c_str(), enode->getText().c_str());
7396 // Not sure if we can get negative heights in the main flow
7397 // (but when we did, because of bugs?, it resulted in hundreds
7398 // of blank pages).
7399 // Getting a zero height may prevent the block and its children
7400 // from being drawn, which is fine for node with no real content.
7401 // But we can rightfully get a negative height for floatBoxes
7402 // whose content has negative margins and is fully outside
7403 // the floatBox.
7404 // So, fix height as needed
7405 if ( is_floating ) {
7406 // Allow it to be candidate for drawing, and have a minimal
7407 // height so it's not just ignored.
7408 h = 1;
7409 bottom_overflow -= 1;
7410 }
7411 else { // Assume no content and nothing to draw
7412 h = 0;
7413 }
7414 }
7415
7416 // Finally, get the height used by our padding and children.
7417 // Original fmt.setY() might have been updated by collapsing margins,
7418 // but we got the real final height.
7419 fmt.setHeight( h );
7420 fmt.setTopOverflow( top_overflow );
7421 fmt.setBottomOverflow( bottom_overflow );
7422 fmt.push();
7423 // if (top_overflow > 0) printf("block top_overflow=%d\n", top_overflow);
7424 // if (bottom_overflow > 0) printf("block bottom_overflow=%d\n", bottom_overflow);
7425
7426 flow->addVerticalMargin( enode, margin_bottom, break_after );
7427 if ( no_margin_collapse ) {
7428 // Push our margin so it does not get collapsed with some later one
7429 flow->pushVerticalMargin();
7430 }
7431 return;
7432 }
7433 break;
7434 case erm_final:
7435 {
7436 // Deal with list item marker
7437 int list_marker_padding = 0;;
7438 if ( style->display == css_d_list_item_block ) {
7439 // list_item_block rendered as final (containing only text and inline elements)
7440 if ( ! renderAsListStylePositionInside(style, is_rtl) ) {
7441 // When list_style_position = outside, we have to shift the final block
7442 // to the right (or to the left if RTL) and reduce its width
7443 lString32 marker = renderListItemMarker( enode, list_marker_padding, NULL, -1, 0 );
7444 // With css_lsp_outside, the marker is outside: it shifts x left and reduces width
7445 width -= list_marker_padding;
7446 fmt.setWidth( width );
7447 // when is_rtl, just reducing width is enough
7448 if (!is_rtl)
7449 fmt.setX( fmt.getX() + list_marker_padding );
7450 fmt.push();
7451 }
7452 }
7453 // Deal with negative text-indent
7454 if ( style->text_indent.value < 0 ) {
7455 int indent = - lengthToPx(style->text_indent, container_width, em);
7456 // We'll need to have text written this positive distance outside
7457 // the nominal text inner_width.
7458 // We can remove it from left padding if indent is smaller than padding.
7459 // If it is larger, we can't remove the excess from left margin, as
7460 // these margin should stay fixed for proper background drawing in their
7461 // limits (the text with negative text-indent should overflow the
7462 // margin and background color).
7463 // But, even if CSS forbids negative padding, the followup code might
7464 // be just fine with negative values for padding_left/_right !
7465 // (Not super sure of that, but it looks like it works, so let's
7466 // go with it - if issues, one can switch to a rendering mode
7467 // without the ALLOW_HORIZONTAL_BLOCK_OVERFLOW flag).
7468 // (Text selection on the overflowing text may not work, but it's
7469 // the same for negative margins.)
7470 if ( !is_rtl ) {
7471 padding_left -= indent;
7472 if ( padding_left < 0 ) {
7473 if ( !BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_BLOCK_OVERFLOW) ) {
7474 padding_left = 0; // be safe, drop excessive part of indent
7475 }
7476 else if ( !BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_PAGE_OVERFLOW) ) {
7477 // Limit to top node (page, float) left margin
7478 int abs_x = flow->getCurrentAbsoluteX();
7479 if ( abs_x + padding_left < 0 )
7480 padding_left = -abs_x;
7481 }
7482 }
7483 }
7484 else {
7485 padding_right -= indent;
7486 if ( padding_right < 0 ) {
7487 if ( !BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_BLOCK_OVERFLOW) ) {
7488 padding_right = 0;
7489 }
7490 else if ( !BLOCK_RENDERING(flags, ALLOW_HORIZONTAL_PAGE_OVERFLOW) ) {
7491 int o_width = flow->getOriginalContainerWidth();
7492 int abs_x = flow->getCurrentAbsoluteX();
7493 if ( abs_x + width + padding_right < o_width )
7494 padding_right = o_width - width - abs_x;
7495 }
7496 }
7497 }
7498 }
7499
7500 // To get an accurate BlockFloatFootprint, we need to push vertical
7501 // margin now (and not delay it to the first addContentLine()).
7502 // This can mess with proper margins collapsing if we were to
7503 // output no content (we don't know that yet).
7504 // So, do it only if we have floats.
7505 if ( flow->hasActiveFloats() )
7506 flow->pushVerticalMargin();
7507
7508 // Store these in RenderRectAccessor fields, to avoid having to
7509 // compute them again when in XPointer, elementFromPoint...
7510 int inner_width = width - padding_left - padding_right;
7511 if (inner_width <= 0) {
7512 // inner_width is the width given to LFormattedText->Format()
7513 // to lay out inlines and text along this width.
7514 // Legacy code allows it to be negative, but it ends
7515 // up being cast'ed to a signed int, resulting in a
7516 // huge width, with possibly text laid out on a single
7517 // long overflowing line.
7518 // We prefer here to make it positive with a limited
7519 // width, still possibly overflowing screen, but allowing
7520 // more text to be seen.
7521 // (This will mess with BlockFloatFootprint and proper
7522 // layout of floats, but we're in some edgy situation.)
7523 if ( width - padding_left > 0 ) {
7524 // Just kill padding_right
7525 inner_width = width - padding_left;
7526 padding_right = 0;
7527 }
7528 else if ( width > 0 ) {
7529 // Just kill both paddings
7530 inner_width = width - padding_left;
7531 padding_left = padding_right = 0;
7532 }
7533 else {
7534 // Kill padding and switch to the top container
7535 // width (the page width)
7536 inner_width = flow->getOriginalContainerWidth();
7537 padding_left = padding_right = 0;
7538 }
7539 // We could also do like Firefox and use the minimal content width:
7540 // int max_content_width = 0;
7541 // int min_content_width = 0;
7542 // getRenderedWidths(enode, max_content_width, min_content_width, true, flags);
7543 // inner_width = min_content_width;
7544 // but it is costly and may result in adding many many pages
7545 // with a narrow column of words.
7546 }
7547 fmt.setInnerX( padding_left );
7548 fmt.setInnerY( padding_top );
7549 fmt.setInnerWidth( inner_width );
7550 RENDER_RECT_SET_FLAG(fmt, INNER_FIELDS_SET);
7551 // Usable overflow for glyphs and hanging punctuation
7552 int usable_overflow_left = flow->getUsableLeftOverflow() + margin_left;
7553 int usable_overflow_right = flow->getUsableRightOverflow() + margin_right;
7554 if ( style->background_color.type == css_val_color || !style->background_image.empty() ) {
7555 // New (or same) background color specified (we assume there is
7556 // a color change): avoid glyphs/hanging punctuation from leaking
7557 // over the background change.
7558 usable_overflow_left = padding_left;
7559 usable_overflow_right = padding_right;
7560 }
7561 // If there's some border, avoid glyphs/hanging punctuation from
7562 // leaking on or over the border.
7563 if ( border_left ) {
7564 usable_overflow_left = padding_left - border_left;
7565 }
7566 if ( border_right ) {
7567 usable_overflow_right = padding_right - border_right;
7568 }
7569 fmt.setUsableLeftOverflow( usable_overflow_left );
7570 fmt.setUsableRightOverflow( usable_overflow_right );
7571 // Done with updating RenderRectAccessor fields, have them saved
7572 fmt.push();
7573 // (These setInner* needs to be set before creating float_footprint if
7574 // we want to debug/valide floatIds coordinates)
7575
7576 // Outer block floats may be drawn over the erm_final node rect.
7577 // Only its text (and embedded floats) must be laid out outside
7578 // these outer floats areas (left and right paddings should be
7579 // left under the floats, and should not be ensured after the
7580 // outer floats areas).
7581 // We will provide the text formatter with a small BlockFloatFootprint
7582 // object, that will provide at most 5 rectangles representing outer
7583 // floats (either real floats, or fake floats).
7584 BlockFloatFootprint float_footprint = flow->getFloatFootprint( enode,
7585 margin_left + (is_rtl ? 0: list_marker_padding) + padding_left,
7586 margin_right + (is_rtl ? list_marker_padding : 0) + padding_right,
7587 padding_top );
7588 // (No need to account for margin-top, as we pushed vertical margin
7589 // just above if there were floats.)
7590
7591 int final_h = enode->renderFinalBlock( txform, &fmt, inner_width, &float_footprint );
7592 int final_min_y = float_footprint.getFinalMinY();
7593 int final_max_y = float_footprint.getFinalMaxY();
7594
7595 flow->getPageContext()->updateRenderProgress(1);
7596 #ifdef DEBUG_DUMP_ENABLED
7597 logfile << "\n";
7598 #endif
7599
7600 int pad_style_h = 0;
7601 if (style_h >= 0) { // computed above
7602 int pad_h = style_h - (final_h + padding_top + padding_bottom);
7603 if (pad_h > 0) {
7604 // don't pad more than one page height
7605 if (pad_h > flow->getPageHeight())
7606 pad_h = flow->getPageHeight();
7607 pad_style_h = pad_h; // to be context.AddLine() below
7608 }
7609 }
7610
7611 int h = padding_top + final_h + pad_style_h + padding_bottom;
7612 final_min_y += padding_top;
7613 final_max_y += padding_top;
7614 int top_overflow = final_min_y < 0 ? -final_min_y : 0;
7615 int bottom_overflow = final_max_y > h ? final_max_y - h : 0;
7616 // if (top_overflow > 0) printf("final top_overflow=%d\n", top_overflow);
7617 // if (bottom_overflow > 0) printf("final bottom_overflow=%d\n", bottom_overflow);
7618
7619 // Reload fmt, as enode->renderFinalBlock() will have
7620 // updated it to store the float_footprint rects in.
7621 // Note: beware RenderRectAccessor's own state and refresh
7622 // management (push/_modified/_dirty) which may not be
7623 // super safe (push() sets dirty only if modified,
7624 // a getX() resets _dirty, and you can't set _dirty
7625 // explicitely when you know a 2nd instance will be
7626 // created and will modify the state).
7627 // So, safer to create a new instance to be sure
7628 // to get fresh data.
7629 fmt = RenderRectAccessor( enode );
7630 fmt.setHeight( h );
7631 fmt.setTopOverflow( top_overflow );
7632 fmt.setBottomOverflow( bottom_overflow );
7633 fmt.push();
7634 // (We set the height now because we know it, but it should be
7635 // equal to what we will addContentLine/addContentSpace below.)
7636
7637 // We need to forward our overflow for it to be carried
7638 // by our block containers if we overflow them.
7639 flow->updateCurrentLevelTopOverflow(top_overflow);
7640
7641 if (padding_top>0) {
7642 // This may add accumulated margin
7643 flow->addContentLine(padding_top, RN_SPLIT_AFTER_AVOID, 0, true);
7644 }
7645
7646 // Enter footnote body after padding, to get rid of it
7647 // and have lean in-page footnotes
7648 if ( isFootNoteBody ) {
7649 // If no padding were added, add an explicite 0-padding so that
7650 // any accumulated vertical margin is pushed here, and not
7651 // part of the footnote
7652 if (padding_top==0) {
7653 flow->addContentLine(0, RN_SPLIT_AFTER_AVOID, 0, true);
7654 }
7655 flow->getPageContext()->enterFootNote( footnoteId );
7656 }
7657
7658 // We have lines of text in 'txform', that we should register
7659 // into flow/context for later page splitting.
7660 int count = txform->GetLineCount();
7661 int orphans = (int)(style->orphans) - (int)(css_orphans_widows_1) + 1;
7662 int widows = (int)(style->widows) - (int)(css_orphans_widows_1) + 1;
7663 for (int i=0; i<count; i++) {
7664 const formatted_line_t * line = txform->GetLineInfo(i);
7665 int line_flags = 0;
7666
7667 // We let the first line with allow split before,
7668 // and the last line with allow split after (padding
7669 // top and bottom will too, but will themselves stick
7670 // to the first and last lines).
7671 // flow->addContentLine() may change that depending on
7672 // surroundings.
7673
7674 // Honor widows and orphans
7675 if (orphans > 1 && i > 0 && i < orphans)
7676 // with orphans:2, and we're the 2nd line (i=1), avoid split before
7677 // so we stick to first line
7678 line_flags |= RN_SPLIT_BEFORE_AVOID;
7679 if (widows > 1 && i < count-1 && count-1 - i < widows)
7680 // with widows:2, and we're the last before last line (i=count-2),
7681 // avoid split after so we stick to last line
7682 line_flags |= RN_SPLIT_AFTER_AVOID;
7683
7684 // Honor line's own flags (used when filling space when
7685 // clearing floats)
7686 if (line->flags & LTEXT_LINE_SPLIT_AVOID_BEFORE)
7687 line_flags |= RN_SPLIT_BEFORE_AVOID;
7688 if (line->flags & LTEXT_LINE_SPLIT_AVOID_AFTER)
7689 line_flags |= RN_SPLIT_AFTER_AVOID;
7690
7691 // Honor our own "page-break-inside: avoid" that hasn't been
7692 // passed to "flow" (any upper "break-inside: avoid" will be
7693 // enforced by flow->addContentLine())
7694 if ( break_inside == RN_SPLIT_AVOID ) {
7695 if (i > 0)
7696 line_flags |= RN_SPLIT_BEFORE_AVOID;
7697 if (i < count-1)
7698 line_flags |= RN_SPLIT_AFTER_AVOID;
7699 }
7700
7701 flow->addContentLine(line->height, line_flags, line->baseline);
7702
7703 // See if there are links to footnotes in that line, and add
7704 // a reference to it so page splitting can bring the footnotes
7705 // text on this page, and then decide about page split.
7706 if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) { // disable footnotes for footnotes
7707 // If paragraph is RTL, we are meeting words in the reverse of the reading order:
7708 // so, insert each link for this line at the same position, instead of at the end.
7709 int link_insert_pos = -1; // append
7710 if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) {
7711 link_insert_pos = flow->getPageContext()->getCurrentLinksCount();
7712 }
7713 for ( int w=0; w<line->word_count; w++ ) {
7714 // check link start flag for every word
7715 if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) {
7716 const src_text_fragment_t * src = txform->GetSrcInfo( line->words[w].src_text_index );
7717 if ( src && src->object ) {
7718 ldomNode * node = (ldomNode*)src->object;
7719 ldomNode * parent = node->getParentNode();
7720 while (parent && parent->getNodeId() != el_a)
7721 parent = parent->getParentNode();
7722 if ( parent && parent->hasAttribute(LXML_NS_ANY, attr_href ) ) {
7723 // was: && parent->getAttributeValue(LXML_NS_ANY, attr_type ) == "note")
7724 // but we want to be able to gather in-page footnotes by only
7725 // specifying a -cr-hint: to the footnote target, with no need
7726 // to set one to the link itself
7727 lString32 href = parent->getAttributeValue(LXML_NS_ANY, attr_href );
7728 if ( href.length()>0 && href.at(0)=='#' ) {
7729 href.erase(0,1);
7730 flow->getPageContext()->addLink( href, link_insert_pos );
7731 }
7732 }
7733 }
7734 }
7735 }
7736 // Also add links gathered by floats in their content
7737 int fcount = txform->GetFloatCount();
7738 for (int f=0; f<fcount; f++) {
7739 const embedded_float_t * flt = txform->GetFloatInfo(f);
7740 if ( i < count-1 ) { // Unless we're the last line:
7741 // ignore this float if it ends after currently processed line
7742 if ( flt->y + flt->height > line->y + line->height ) {
7743 continue;
7744 }
7745 }
7746 if ( flt->links && flt->links->length() > 0 ) {
7747 for ( int n=0; n<flt->links->length(); n++ ) {
7748 flow->getPageContext()->addLink( flt->links->at(n) );
7749 }
7750 flt->links->clear(); // don't reprocess them if float met again with next lines
7751 }
7752 }
7753 }
7754 }
7755
7756 // Leave footnote body before style height and padding, to get
7757 // rid of them and have lean in-page footnotes
7758 if ( isFootNoteBody )
7759 flow->getPageContext()->leaveFootNote();
7760
7761 if( pad_style_h > 0) {
7762 // Add filling space to the page splitting context
7763 // Allow page splitting inside that useless excessive style height
7764 flow->addContentSpace(pad_style_h, 1, false, false, false);
7765 }
7766
7767 if (padding_bottom>0) {
7768 flow->addContentLine(padding_bottom, RN_SPLIT_BEFORE_AVOID, 0, true);
7769 }
7770
7771 // We need to forward our overflow for it to be carried
7772 // by our block containers if we overflow them.
7773 flow->updateCurrentLevelBottomOverflow(bottom_overflow);
7774
7775 flow->addVerticalMargin( enode, margin_bottom, break_after );
7776 if ( no_margin_collapse ) {
7777 // Push our margin so it does not get collapsed with some later one
7778 flow->pushVerticalMargin();
7779 }
7780 return;
7781 }
7782 break;
7783 case erm_killed:
7784 {
7785 // DrawDocument will render a small figure in this rect area
7786 fmt.setHeight( 15 ); // not squared, so it does not look
7787 fmt.setWidth( 10 ); // like a list square bullet
7788 // Let it be at the x/y decided above
7789 flow->addContentLine( fmt.getHeight(), 0, fmt.getHeight() );
7790 return;
7791 }
7792 break;
7793 default:
7794 CRLog::error("Unsupported render method %d", m);
7795 crFatalError(141, "Unsupported render method"); // error
7796 break;
7797 }
7798 return;
7799 }
7800
7801 // Entry points for rendering the root node, a table cell or a float
renderBlockElement(LVRendPageContext & context,ldomNode * enode,int x,int y,int width,int usable_left_overflow,int usable_right_overflow,int direction,int * baseline,lUInt32 rend_flags)7802 int renderBlockElement(LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
7803 int usable_left_overflow, int usable_right_overflow, int direction, int * baseline, lUInt32 rend_flags )
7804 {
7805 if ( BLOCK_RENDERING(rend_flags, ENHANCED) ) {
7806 // Create a flow state (aka "block formatting context") for the rendering
7807 // of this block and all its children.
7808 // (We are called when rendering the root node, and when rendering each float
7809 // met along walking the root node hierarchy - and when meeting a new float
7810 // in a float, etc...)
7811 FlowState flow( context, width, usable_left_overflow, usable_right_overflow, rend_flags,
7812 direction, TextLangMan::getLangNodeIndex(enode) );
7813 flow.moveDown(y);
7814 if (baseline != NULL) {
7815 flow.setRequestedBaselineType(*baseline);
7816 }
7817 renderBlockElementEnhanced( &flow, enode, x, width, rend_flags );
7818 if (baseline != NULL) {
7819 // (We pass the top node, so it can find the first table row
7820 // if needed with inline-table.)
7821 *baseline = flow.getBaselineAbsoluteY(enode);
7822 }
7823 // The block height is c_y when we are done
7824 return flow.getCurrentAbsoluteY();
7825 } else {
7826 // (Legacy rendering does not support direction)
7827 return renderBlockElementLegacy( context, enode, x, y, width, usable_right_overflow);
7828 }
7829 }
renderBlockElement(LVRendPageContext & context,ldomNode * enode,int x,int y,int width,int usable_left_overflow,int usable_right_overflow,int direction,int * baseline)7830 int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
7831 int usable_left_overflow, int usable_right_overflow, int direction, int * baseline )
7832 {
7833 return renderBlockElement( context, enode, x, y, width, usable_left_overflow, usable_right_overflow,
7834 direction, baseline, enode->getDocument()->getRenderBlockRenderingFlags() );
7835 }
7836
7837 //draw border lines,support color,width,all styles, not support border-collapse
DrawBorder(ldomNode * enode,LVDrawBuf & drawbuf,int x0,int y0,int doc_x,int doc_y,RenderRectAccessor fmt)7838 void DrawBorder(ldomNode *enode,LVDrawBuf & drawbuf,int x0,int y0,int doc_x,int doc_y,RenderRectAccessor fmt)
7839 {
7840 css_style_ref_t style = enode->getStyle();
7841 bool hastopBorder = (style->border_style_top >=css_border_solid&&style->border_style_top<=css_border_outset);
7842 bool hasrightBorder = (style->border_style_right >=css_border_solid&&style->border_style_right<=css_border_outset);
7843 bool hasbottomBorder = (style->border_style_bottom >=css_border_solid&&style->border_style_bottom<=css_border_outset);
7844 bool hasleftBorder = (style->border_style_left >=css_border_solid&&style->border_style_left<=css_border_outset);
7845
7846 // Check for explicit 'border-width: 0' which means no border.
7847 css_length_t bw;
7848 bw = style->border_width[0];
7849 hastopBorder = hastopBorder & !(bw.value == 0 && bw.type > css_val_unspecified);
7850 bw = style->border_width[1];
7851 hasrightBorder = hasrightBorder & !(bw.value == 0 && bw.type > css_val_unspecified);
7852 bw = style->border_width[2];
7853 hasbottomBorder = hasbottomBorder & !(bw.value == 0 && bw.type > css_val_unspecified);
7854 bw = style->border_width[3];
7855 hasleftBorder = hasleftBorder & !(bw.value == 0 && bw.type > css_val_unspecified);
7856
7857 // Check for explicit 'border-color: transparent' which means no border to draw
7858 css_length_t bc;
7859 bc = style->border_color[0];
7860 hastopBorder = hastopBorder & !(bc.type == css_val_unspecified && bc.value == css_generic_transparent);
7861 bc = style->border_color[1];
7862 hasrightBorder = hasrightBorder & !(bc.type == css_val_unspecified && bc.value == css_generic_transparent);
7863 bc = style->border_color[2];
7864 hasbottomBorder = hasbottomBorder & !(bc.type == css_val_unspecified && bc.value == css_generic_transparent);
7865 bc = style->border_color[3];
7866 hasleftBorder = hasleftBorder & !(bc.type == css_val_unspecified && bc.value == css_generic_transparent);
7867
7868 if (hasbottomBorder || hasleftBorder || hasrightBorder || hastopBorder) {
7869 lUInt32 shadecolor=0x555555;
7870 lUInt32 lightcolor=0xAAAAAA;
7871 int em = enode->getFont()->getSize();
7872 int width = 0; // values in % are invalid for borders, so we shouldn't get any
7873 int topBorderwidth = lengthToPx(style->border_width[0],width,em);
7874 topBorderwidth = topBorderwidth!=0 ? topBorderwidth : DEFAULT_BORDER_WIDTH;
7875 int rightBorderwidth = lengthToPx(style->border_width[1],width,em);
7876 rightBorderwidth = rightBorderwidth!=0 ? rightBorderwidth : DEFAULT_BORDER_WIDTH;
7877 int bottomBorderwidth = lengthToPx(style->border_width[2],width,em);
7878 bottomBorderwidth = bottomBorderwidth!=0 ? bottomBorderwidth : DEFAULT_BORDER_WIDTH;
7879 int leftBorderwidth = lengthToPx(style->border_width[3],width,em);
7880 leftBorderwidth = leftBorderwidth!=0 ? leftBorderwidth : DEFAULT_BORDER_WIDTH;
7881 int tbw=topBorderwidth,rbw=rightBorderwidth,bbw=bottomBorderwidth,lbw=leftBorderwidth;
7882 if (hastopBorder) {
7883 int dot=1,interval=0;//default style
7884 lUInt32 topBordercolor = style->border_color[0].value;
7885 topBorderwidth=tbw;
7886 rightBorderwidth=rbw;
7887 // bottomBorderwidth=bbw; // (not used)
7888 leftBorderwidth=lbw;
7889 if (style->border_color[0].type==css_val_color)
7890 {
7891 lUInt32 r,g,b;
7892 r=g=b=topBordercolor;
7893 r=r>>16;
7894 g=g>>8&0xff;
7895 b=b&0xff;
7896 shadecolor=(r*160/255)<<16|(g*160/255)<<8|b*160/255;
7897 lightcolor=topBordercolor;
7898 }
7899 int left=1,right=1;
7900 left=(hasleftBorder)?0:1;
7901 right=(hasrightBorder)?0:1;
7902 left=(style->border_style_left==css_border_dotted||style->border_style_left==css_border_dashed)?0:left;
7903 right=(style->border_style_right==css_border_dotted||style->border_style_right==css_border_dashed)?0:right;
7904 lvPoint leftpoint1=lvPoint(x0+doc_x,y0+doc_y),
7905 leftpoint2=lvPoint(x0+doc_x,y0+doc_y+0.5*topBorderwidth),
7906 leftpoint3=lvPoint(x0+doc_x,doc_y+y0+topBorderwidth),
7907 rightpoint1=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0),
7908 rightpoint2=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+0.5*topBorderwidth),
7909 rightpoint3=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+topBorderwidth);
7910 double leftrate=1,rightrate=1;
7911 if (left==0) {
7912 leftpoint1.x=x0+doc_x;
7913 leftpoint1.y=doc_y+y0;
7914 leftpoint2.x=x0+doc_x+0.5*leftBorderwidth;
7915 leftpoint2.y=doc_y+y0+0.5*topBorderwidth;
7916 leftpoint3.x=x0+doc_x+leftBorderwidth;
7917 leftpoint3.y=doc_y+y0+topBorderwidth;
7918 }else leftBorderwidth=0;
7919 leftrate=(double)leftBorderwidth/(double)topBorderwidth;
7920 if (right==0) {
7921 rightpoint1.x=x0+doc_x+fmt.getWidth()-1;
7922 rightpoint1.y=doc_y+y0;
7923 rightpoint2.x=x0+doc_x+fmt.getWidth()-1-0.5*rightBorderwidth;
7924 rightpoint2.y=doc_y+y0+0.5*topBorderwidth;
7925 rightpoint3.x=x0+doc_x+fmt.getWidth()-1-rightBorderwidth;
7926 rightpoint3.y=doc_y+y0+topBorderwidth;
7927 } else rightBorderwidth=0;
7928 rightrate=(double)rightBorderwidth/(double)topBorderwidth;
7929 switch (style->border_style_top){
7930 case css_border_dotted:
7931 dot=interval=topBorderwidth;
7932 for(int i=0;i<leftpoint3.y-leftpoint1.y;i++)
7933 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7934 rightpoint1.y+i+1, topBordercolor,dot,interval,0);}
7935 break;
7936 case css_border_dashed:
7937 dot=3*topBorderwidth;
7938 interval=3*topBorderwidth;
7939 for(int i=0;i<leftpoint3.y-leftpoint1.y;i++)
7940 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7941 rightpoint1.y+i+1, topBordercolor,dot,interval,0);}
7942 break;
7943 case css_border_solid:
7944 for(int i=0;i<leftpoint3.y-leftpoint1.y;i++)
7945 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7946 rightpoint1.y+i+1, topBordercolor,dot,interval,0);}
7947 break;
7948 case css_border_double:
7949 for(int i=0;i<=(leftpoint2.y-leftpoint1.y)/(leftpoint2.y-leftpoint1.y>2?3:2);i++)
7950 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7951 rightpoint1.y+i+1, topBordercolor);}
7952 for(int i=0;i<=(leftpoint3.y-leftpoint2.y)/(leftpoint3.y-leftpoint2.y>2?3:2);i++)
7953 {drawbuf.FillRect(leftpoint3.x-i*leftrate, leftpoint3.y-i, rightpoint3.x+i*rightrate+1,
7954 rightpoint3.y-i+1, topBordercolor);}
7955 break;
7956 case css_border_groove:
7957 for(int i=0;i<=leftpoint2.y-leftpoint1.y;i++)
7958 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7959 rightpoint1.y+i+1, shadecolor);}
7960 for(int i=0;i<leftpoint3.y-leftpoint2.y;i++)
7961 {drawbuf.FillRect(leftpoint2.x+i*leftrate, leftpoint2.y+i, rightpoint2.x-i*rightrate+1,
7962 rightpoint2.y+i+1, lightcolor);}
7963 break;
7964 case css_border_inset:
7965 for(int i=0;i<leftpoint3.y-leftpoint1.y;i++)
7966 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7967 rightpoint1.y+i+1, shadecolor,dot,interval,0);}
7968 break;
7969 case css_border_outset:
7970 for(int i=0;i<leftpoint3.y-leftpoint1.y;i++)
7971 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7972 rightpoint1.y+i+1, lightcolor,dot,interval,0);}
7973 break;
7974 case css_border_ridge:
7975 for(int i=0;i<=leftpoint2.y-leftpoint1.y;i++)
7976 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y+i, rightpoint1.x-i*rightrate+1,
7977 rightpoint1.y+i+1, lightcolor);}
7978 for(int i=0;i<leftpoint3.y-leftpoint2.y;i++)
7979 {drawbuf.FillRect(leftpoint2.x+i*leftrate, leftpoint2.y+i, rightpoint2.x-i*rightrate+1,
7980 rightpoint2.y+i+1, shadecolor);}
7981 break;
7982 default:
7983 break;
7984 }
7985 }
7986 //right
7987 if (hasrightBorder) {
7988 int dot=1,interval=0;//default style
7989 lUInt32 rightBordercolor = style->border_color[1].value;
7990 topBorderwidth=tbw;
7991 rightBorderwidth=rbw;
7992 bottomBorderwidth=bbw;
7993 // leftBorderwidth=lbw; // (not used)
7994 if (style->border_color[1].type==css_val_color)
7995 {
7996 lUInt32 r,g,b;
7997 r=g=b=rightBordercolor;
7998 r=r>>16;
7999 g=g>>8&0xff;
8000 b=b&0xff;
8001 shadecolor=(r*160/255)<<16|(g*160/255)<<8|b*160/255;
8002 lightcolor=rightBordercolor;
8003 }
8004 int up=1,down=1;
8005 up=(hastopBorder)?0:1;
8006 down=(hasbottomBorder)?0:1;
8007 up=(style->border_style_top==css_border_dotted||style->border_style_top==css_border_dashed)?1:up;
8008 down=(style->border_style_bottom==css_border_dotted||style->border_style_bottom==css_border_dashed)?1:down;
8009 lvPoint toppoint1=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0),
8010 toppoint2=lvPoint(x0+doc_x+fmt.getWidth()-1-0.5*rightBorderwidth,doc_y+y0),
8011 toppoint3=lvPoint(x0+doc_x+fmt.getWidth()-1-rightBorderwidth,doc_y+y0),
8012 bottompoint1=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+fmt.getHeight()-1),
8013 bottompoint2=lvPoint(x0+doc_x+fmt.getWidth()-1-0.5*rightBorderwidth,doc_y+y0+fmt.getHeight()-1),
8014 bottompoint3=lvPoint(x0+doc_x+fmt.getWidth()-1-rightBorderwidth,doc_y+y0+fmt.getHeight()-1);
8015 double toprate=1,bottomrate=1;
8016 if (up==0) {
8017 toppoint3.y=doc_y+y0+topBorderwidth;
8018 toppoint2.y=doc_y+y0+0.5*topBorderwidth;
8019 } else topBorderwidth=0;
8020 toprate=(double)topBorderwidth/(double)rightBorderwidth;
8021 if (down==0) {
8022 bottompoint3.y=y0+doc_y+fmt.getHeight()-1-bottomBorderwidth;
8023 bottompoint2.y=y0+doc_y+fmt.getHeight()-1-0.5*bottomBorderwidth;
8024 } else bottomBorderwidth=0;
8025 bottomrate=(double)bottomBorderwidth/(double)rightBorderwidth;
8026 switch (style->border_style_right){
8027 case css_border_dotted:
8028 dot=interval=rightBorderwidth;
8029 for (int i=0;i<toppoint1.x-toppoint3.x;i++){
8030 drawbuf.DrawLine(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8031 bottompoint1.y-i*bottomrate+1, rightBordercolor,dot,interval,1);
8032 }
8033 break;
8034 case css_border_dashed:
8035 dot=3*rightBorderwidth;
8036 interval=3*rightBorderwidth;
8037 for (int i=0;i<toppoint1.x-toppoint3.x;i++){
8038 drawbuf.DrawLine(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8039 bottompoint1.y-i*bottomrate+1, rightBordercolor,dot,interval,1);
8040 }
8041 break;
8042 case css_border_solid:
8043 for (int i=0;i<toppoint1.x-toppoint3.x;i++){
8044 drawbuf.DrawLine(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8045 bottompoint1.y-i*bottomrate+1, rightBordercolor,dot,interval,1);
8046 }
8047 break;
8048 case css_border_double:
8049 for (int i=0;i<=(toppoint1.x-toppoint2.x)/(toppoint1.x-toppoint2.x>2?3:2);i++){
8050 drawbuf.FillRect(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8051 bottompoint1.y-i*bottomrate+1, rightBordercolor);
8052 }
8053 for (int i=0;i<=(toppoint2.x-toppoint3.x)/(toppoint2.x-toppoint3.x>2?3:2);i++){
8054 drawbuf.FillRect(toppoint3.x+i,toppoint3.y-i*toprate,bottompoint3.x+i+1,
8055 bottompoint3.y+i*bottomrate+1, rightBordercolor);
8056 }
8057 break;
8058 case css_border_groove:
8059 for (int i=0;i<toppoint1.x-toppoint2.x;i++){
8060 drawbuf.FillRect(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8061 bottompoint1.y-i*bottomrate+1, lightcolor);
8062 }
8063 for (int i=0;i<=toppoint2.x-toppoint3.x;i++){
8064 drawbuf.FillRect(toppoint2.x-i,toppoint2.y+i*toprate,bottompoint2.x-i+1,
8065 bottompoint2.y-i*bottomrate+1, shadecolor);
8066 }
8067 break;
8068 case css_border_inset:
8069 for (int i=0;i<toppoint1.x-toppoint3.x;i++){
8070 drawbuf.DrawLine(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8071 bottompoint1.y-i*bottomrate+1, lightcolor,dot,interval,1);
8072 }
8073 break;
8074 case css_border_outset:
8075 for (int i=0;i<toppoint1.x-toppoint3.x;i++){
8076 drawbuf.DrawLine(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8077 bottompoint1.y-i*bottomrate+1, shadecolor,dot,interval,1);
8078 }
8079 break;
8080 case css_border_ridge:
8081 for (int i=0;i<toppoint1.x-toppoint2.x;i++){
8082 drawbuf.FillRect(toppoint1.x-i,toppoint1.y+i*toprate,bottompoint1.x-i+1,
8083 bottompoint1.y-i*bottomrate+1, shadecolor);
8084 }
8085 for (int i=0;i<=toppoint2.x-toppoint3.x;i++){
8086 drawbuf.FillRect(toppoint2.x-i,toppoint2.y+i*toprate,bottompoint2.x-i+1,
8087 bottompoint2.y-i*bottomrate+1,lightcolor);
8088 }
8089 break;
8090 default:break;
8091 }
8092 }
8093 //bottom
8094 if (hasbottomBorder) {
8095 int dot=1,interval=0;//default style
8096 lUInt32 bottomBordercolor = style->border_color[2].value;
8097 // topBorderwidth=tbw; // (not used)
8098 rightBorderwidth=rbw;
8099 bottomBorderwidth=bbw;
8100 leftBorderwidth=lbw;
8101 if (style->border_color[2].type==css_val_color)
8102 {
8103 lUInt32 r,g,b;
8104 r=g=b=bottomBordercolor;
8105 r=r>>16;
8106 g=g>>8&0xff;
8107 b=b&0xff;
8108 shadecolor=(r*160/255)<<16|(g*160/255)<<8|b*160/255;
8109 lightcolor=bottomBordercolor;
8110 }
8111 int left=1,right=1;
8112 left=(hasleftBorder)?0:1;
8113 right=(hasrightBorder)?0:1;
8114 left=(style->border_style_left==css_border_dotted||style->border_style_left==css_border_dashed)?1:left;
8115 right=(style->border_style_right==css_border_dotted||style->border_style_right==css_border_dashed)?1:right;
8116 lvPoint leftpoint1=lvPoint(x0+doc_x,y0+doc_y+fmt.getHeight()-1),
8117 leftpoint2=lvPoint(x0+doc_x,y0+doc_y-0.5*bottomBorderwidth+fmt.getHeight()-1),
8118 leftpoint3=lvPoint(x0+doc_x,doc_y+y0+fmt.getHeight()-1-bottomBorderwidth),
8119 rightpoint1=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+fmt.getHeight()-1),
8120 rightpoint2=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+fmt.getHeight()-1-0.5*bottomBorderwidth),
8121 rightpoint3=lvPoint(x0+doc_x+fmt.getWidth()-1,doc_y+y0+fmt.getHeight()-1-bottomBorderwidth);
8122 double leftrate=1,rightrate=1;
8123 if (left==0) {
8124 leftpoint3.x=x0+doc_x+leftBorderwidth;
8125 leftpoint2.x=x0+doc_x+0.5*leftBorderwidth;
8126 }else leftBorderwidth=0;
8127 leftrate=(double)leftBorderwidth/(double)bottomBorderwidth;
8128 if (right==0) {
8129 rightpoint3.x=x0+doc_x+fmt.getWidth()-1-rightBorderwidth;
8130 rightpoint2.x=x0+doc_x+fmt.getWidth()-1-0.5*rightBorderwidth;
8131 } else rightBorderwidth=0;
8132 rightrate=(double)rightBorderwidth/(double)bottomBorderwidth;
8133 switch (style->border_style_bottom){
8134 case css_border_dotted:
8135 dot=interval=bottomBorderwidth;
8136 for(int i=0;i<leftpoint1.y-leftpoint3.y;i++)
8137 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8138 rightpoint1.y-i+1, bottomBordercolor,dot,interval,0);}
8139 break;
8140 case css_border_dashed:
8141 dot=3*bottomBorderwidth;
8142 interval=3*bottomBorderwidth;
8143 for(int i=0;i<leftpoint1.y-leftpoint3.y;i++)
8144 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8145 rightpoint1.y-i+1, bottomBordercolor,dot,interval,0);}
8146 break;
8147 case css_border_solid:
8148 for(int i=0;i<leftpoint1.y-leftpoint3.y;i++)
8149 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8150 rightpoint1.y-i+1, bottomBordercolor,dot,interval,0);}
8151 break;
8152 case css_border_double:
8153 for(int i=0;i<=(leftpoint1.y-leftpoint2.y)/(leftpoint1.y-leftpoint2.y>2?3:2);i++)
8154 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8155 rightpoint1.y-i+1, bottomBordercolor);}
8156 for(int i=0;i<=(leftpoint2.y-leftpoint3.y)/(leftpoint2.y-leftpoint3.y>2?3:2);i++)
8157 {drawbuf.FillRect(leftpoint3.x-i*leftrate, leftpoint3.y+i, rightpoint3.x+i*rightrate+1,
8158 rightpoint3.y+i+1, bottomBordercolor);}
8159 break;
8160 case css_border_groove:
8161 for(int i=0;i<=leftpoint1.y-leftpoint2.y;i++)
8162 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8163 rightpoint1.y-i+1, lightcolor);}
8164 for(int i=0;i<leftpoint2.y-leftpoint3.y;i++)
8165 {drawbuf.FillRect(leftpoint2.x+i*leftrate, leftpoint2.y-i, rightpoint2.x-i*rightrate+1,
8166 rightpoint2.y-i+1, shadecolor);}
8167 break;
8168 case css_border_inset:
8169 for(int i=0;i<=leftpoint1.y-leftpoint3.y;i++)
8170 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8171 rightpoint1.y-i+1, lightcolor,dot,interval,0);}
8172 break;
8173 case css_border_outset:
8174 for(int i=0;i<=leftpoint1.y-leftpoint3.y;i++)
8175 {drawbuf.DrawLine(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8176 rightpoint1.y-i+1, shadecolor,dot,interval,0);}
8177 break;
8178 case css_border_ridge:
8179 for(int i=0;i<=leftpoint1.y-leftpoint2.y;i++)
8180 {drawbuf.FillRect(leftpoint1.x+i*leftrate, leftpoint1.y-i, rightpoint1.x-i*rightrate+1,
8181 rightpoint1.y-i+1, shadecolor);}
8182 for(int i=0;i<leftpoint2.y-leftpoint3.y;i++)
8183 {drawbuf.FillRect(leftpoint2.x+i*leftrate, leftpoint2.y-i, rightpoint2.x-i*rightrate+1,
8184 rightpoint2.y-i+1, lightcolor);}
8185 break;
8186 default:break;
8187 }
8188 }
8189 //left
8190 if (hasleftBorder) {
8191 int dot=1,interval=0;//default style
8192 lUInt32 leftBordercolor = style->border_color[3].value;
8193 topBorderwidth=tbw;
8194 // rightBorderwidth=rbw; // (not used)
8195 bottomBorderwidth=bbw;
8196 leftBorderwidth=lbw;
8197 if (style->border_color[3].type==css_val_color)
8198 {
8199 lUInt32 r,g,b;
8200 r=g=b=leftBordercolor;
8201 r=r>>16;
8202 g=g>>8&0xff;
8203 b=b&0xff;
8204 shadecolor=(r*160/255)<<16|(g*160/255)<<8|b*160/255;
8205 lightcolor=leftBordercolor;
8206 }
8207 int up=1,down=1;
8208 up=(hastopBorder)?0:1;
8209 down=(hasbottomBorder)?0:1;
8210 up=(style->border_style_top==css_border_dotted||style->border_style_top==css_border_dashed)?1:up;
8211 down=(style->border_style_bottom==css_border_dotted||style->border_style_bottom==css_border_dashed)?1:down;
8212 lvPoint toppoint1=lvPoint(x0+doc_x,doc_y+y0),
8213 toppoint2=lvPoint(x0+doc_x+0.5*leftBorderwidth,doc_y+y0),
8214 toppoint3=lvPoint(x0+doc_x+leftBorderwidth,doc_y+y0),
8215 bottompoint1=lvPoint(x0+doc_x,doc_y+y0+fmt.getHeight()-1),
8216 bottompoint2=lvPoint(x0+doc_x+0.5*leftBorderwidth,doc_y+y0+fmt.getHeight()-1),
8217 bottompoint3=lvPoint(x0+doc_x+leftBorderwidth,doc_y+y0+fmt.getHeight()-1);
8218 double toprate=1,bottomrate=1;
8219 if (up==0) {
8220 toppoint3.y=doc_y+y0+topBorderwidth;
8221 toppoint2.y=doc_y+y0+0.5*topBorderwidth;
8222 } else topBorderwidth=0;
8223 toprate=(double)topBorderwidth/(double)leftBorderwidth;
8224 if (down==0) {
8225 bottompoint3.y=y0+doc_y+fmt.getHeight()-1-bottomBorderwidth;
8226 bottompoint2.y=y0+doc_y+fmt.getHeight()-1-0.5*bottomBorderwidth;
8227 } else bottomBorderwidth=0;
8228 bottomrate=(double)bottomBorderwidth/(double)leftBorderwidth;
8229 switch (style->border_style_left){
8230 case css_border_dotted:
8231 dot=interval=leftBorderwidth;
8232 for (int i=0;i<toppoint3.x-toppoint1.x;i++){
8233 drawbuf.DrawLine(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8234 bottompoint1.y-i*bottomrate+1,leftBordercolor,dot,interval,1);
8235 }
8236 break;
8237 case css_border_dashed:
8238 dot=3*leftBorderwidth;
8239 interval=3*leftBorderwidth;
8240 for (int i=0;i<toppoint3.x-toppoint1.x;i++){
8241 drawbuf.DrawLine(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8242 bottompoint1.y-i*bottomrate+1,leftBordercolor,dot,interval,1);
8243 }
8244 break;
8245 case css_border_solid:
8246 for (int i=0;i<toppoint3.x-toppoint1.x;i++){
8247 drawbuf.DrawLine(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8248 bottompoint1.y-i*bottomrate+1,leftBordercolor,dot,interval,1);
8249 }
8250 break;
8251 case css_border_double:
8252 for (int i=0;i<=(toppoint2.x-toppoint1.x)/(toppoint2.x-toppoint1.x>2?3:2);i++){
8253 drawbuf.FillRect(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8254 bottompoint1.y-i*bottomrate+1,leftBordercolor);
8255 }
8256 for (int i=0;i<=(toppoint3.x-toppoint2.x)/(toppoint3.x-toppoint2.x>2?3:2);i++){
8257 drawbuf.FillRect(toppoint3.x-i,toppoint3.y-i*toprate,bottompoint3.x-i+1,
8258 bottompoint3.y+i*bottomrate+1,leftBordercolor);
8259 }
8260 break;
8261 case css_border_groove:
8262 for (int i=0;i<=toppoint2.x-toppoint1.x;i++){
8263 drawbuf.FillRect(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8264 bottompoint1.y-i*bottomrate+1,shadecolor);
8265 }
8266 for (int i=0;i<toppoint3.x-toppoint2.x;i++){
8267 drawbuf.FillRect(toppoint2.x+i,toppoint2.y+i*toprate,bottompoint2.x+i+1,
8268 bottompoint2.y-i*bottomrate+1,lightcolor);
8269 }
8270 break;
8271 case css_border_inset:
8272 for (int i=0;i<toppoint3.x-toppoint1.x;i++){
8273 drawbuf.DrawLine(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8274 bottompoint1.y-i*bottomrate+1,shadecolor,dot,interval,1);
8275 }
8276 break;
8277 case css_border_outset:
8278 for (int i=0;i<toppoint3.x-toppoint1.x;i++){
8279 drawbuf.DrawLine(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8280 bottompoint1.y-i*bottomrate+1,lightcolor,dot,interval,1);
8281 }
8282 break;
8283 case css_border_ridge:
8284 for (int i=0;i<=toppoint2.x-toppoint1.x;i++){
8285 drawbuf.FillRect(toppoint1.x+i,toppoint1.y+i*toprate,bottompoint1.x+i+1,
8286 bottompoint1.y-i*bottomrate+1,lightcolor);
8287 }
8288 for (int i=0;i<toppoint3.x-toppoint2.x;i++){
8289 drawbuf.FillRect(toppoint2.x+i,toppoint2.y+i*toprate,bottompoint2.x+i+1,
8290 bottompoint2.y-i*bottomrate+1,shadecolor);
8291 }
8292 break;
8293 default:break;
8294 }
8295 }
8296 }
8297 }
DrawBackgroundImage(ldomNode * enode,LVDrawBuf & drawbuf,int x0,int y0,int doc_x,int doc_y,int width,int height)8298 void DrawBackgroundImage(ldomNode *enode,LVDrawBuf & drawbuf,int x0,int y0,int doc_x,int doc_y, int width, int height)
8299 {
8300 // (The provided width and height gives the area we have to draw the background image on)
8301 css_style_ref_t style=enode->getStyle();
8302 if (!style->background_image.empty()) {
8303 lString32 filepath = lString32(style->background_image.c_str());
8304 LVImageSourceRef img = enode->getParentNode()->getDocument()->getObjectImageSource(filepath);
8305 if (!img.isNull()) {
8306 // Native image size
8307 int img_w =img->GetWidth();
8308 int img_h =img->GetHeight();
8309
8310 // See if background-size specified and we need to adjust image native size
8311 css_length_t bg_w = style->background_size[0];
8312 css_length_t bg_h = style->background_size[1];
8313 if ( bg_w.type != css_val_unspecified || bg_w.value != 0 || bg_h.type != css_val_unspecified || bg_h.value != 0 ) {
8314 int new_w = 0;
8315 int new_h = 0;
8316 RenderRectAccessor fmt( enode );
8317 int container_w = fmt.getWidth();
8318 int container_h = fmt.getHeight();
8319 bool check_lengths = true;
8320 if ( bg_w.type == css_val_unspecified && bg_h.type == css_val_unspecified ) {
8321 if ( bg_w.value == css_generic_contain && bg_h.value == css_generic_contain ) {
8322 // Image should be fully contained in container (no crop)
8323 int scale_w = 1024 * container_w / img_w;
8324 int scale_h = 1024 * container_h / img_h;
8325 if ( scale_w < scale_h ) {
8326 new_w = container_w;
8327 new_h = img_h * scale_w / 1024;
8328 }
8329 else {
8330 new_h = container_h;
8331 new_w = img_w * scale_h / 1024;
8332 }
8333 check_lengths = false;
8334 }
8335 else if ( bg_w.value == css_generic_cover && bg_h.value == css_generic_cover ) {
8336 // Image should fully cover container (crop allowed)
8337 int scale_w = 1024 * container_w / img_w;
8338 int scale_h = 1024 * container_h / img_h;
8339 if ( scale_w > scale_h ) {
8340 new_w = container_w;
8341 new_h = img_h * scale_w / 1024;
8342 }
8343 else {
8344 new_h = container_h;
8345 new_w = img_w * scale_h / 1024;
8346 }
8347 check_lengths = false;
8348 }
8349 }
8350 if ( check_lengths ) {
8351 int em = enode->getFont()->getSize();
8352 // These will compute to 0 if (css_val_unspecified, 0) when really not specified
8353 new_w = lengthToPx(style->background_size[0], container_w, em);
8354 new_h = lengthToPx(style->background_size[1], container_h, em);
8355 if ( new_w == 0 ) {
8356 if ( new_h == 0 ) { // keep image native size
8357 new_h = img_h;
8358 new_w = img_w;
8359 }
8360 else { // use style height, keep aspect ratio
8361 new_w = img_w * new_h / img_h;
8362 }
8363 }
8364 else if ( new_h == 0 ) { // use style width, keep aspect ratio
8365 new_h = new_w * img_h / img_w;
8366 }
8367 }
8368 if ( new_w == 0 || new_h == 0 ) {
8369 // width or height computed to 0: nothing to draw
8370 return;
8371 }
8372 if ( new_w != img_w || new_h != img_h ) {
8373 img = LVCreateStretchFilledTransform(img, new_w, new_h, IMG_TRANSFORM_STRETCH, IMG_TRANSFORM_STRETCH, 0, 0);
8374 img_w = new_w;
8375 img_h = new_h;
8376 }
8377 }
8378
8379 // We can use some crengine facilities for background repetition and position,
8380 // which has the advantage that img will be decoded once even if tiling it many
8381 // times and if the target is many screen-heights long (like <BODY> could be).
8382 // Unfortunaly, it does not everything well when not using IMG_TRANSFORM_TILE,
8383 // as it would fill the not-drawn part of the target buffer with garbage,
8384 // instead of letting it as is.
8385 ImageTransform hori_transform = IMG_TRANSFORM_NONE;
8386 ImageTransform vert_transform = IMG_TRANSFORM_NONE;
8387 int transform_w = img_w;
8388 int transform_h = img_h;
8389 switch (style->background_repeat) {
8390 case css_background_no_repeat:
8391 case css_background_repeat_y:
8392 break;
8393 case css_background_repeat_x:
8394 case css_background_repeat:
8395 default:
8396 // No need to tile if image is larger than target
8397 if ( width > img_w ) {
8398 hori_transform = IMG_TRANSFORM_TILE;
8399 transform_w = width;
8400 }
8401 break;
8402 }
8403 switch (style->background_repeat) {
8404 case css_background_no_repeat:
8405 case css_background_repeat_x:
8406 break;
8407 case css_background_repeat_y:
8408 case css_background_repeat:
8409 default:
8410 // No need to tile if image is larger than target
8411 if ( height > img_h ) {
8412 vert_transform = IMG_TRANSFORM_TILE;
8413 transform_h = height;
8414 }
8415 break;
8416 }
8417 // Compute the position where to draw top left of image, as if
8418 // it was a single image when no-repeat
8419 int draw_x = 0;
8420 int draw_y = 0;
8421 switch (style->background_position) {
8422 case css_background_left_top:
8423 case css_background_left_center:
8424 case css_background_left_bottom:
8425 break;
8426 case css_background_center_top:
8427 case css_background_center_center:
8428 case css_background_center_bottom:
8429 draw_x = (width - img_w)/2;
8430 break;
8431 case css_background_right_top:
8432 case css_background_right_center:
8433 case css_background_right_bottom:
8434 draw_x = width - img_w;
8435 break;
8436 default:
8437 break;
8438 }
8439 switch (style->background_position) {
8440 case css_background_left_top:
8441 case css_background_center_top:
8442 case css_background_right_top:
8443 break;
8444 case css_background_left_center:
8445 case css_background_center_center:
8446 case css_background_right_center:
8447 draw_y = (height - img_h)/2;
8448 break;
8449 case css_background_left_bottom:
8450 case css_background_center_bottom:
8451 case css_background_right_bottom:
8452 draw_y = height - img_h;
8453 break;
8454 default:
8455 break;
8456 }
8457 // If tiling, we need to adjust the transform x/y (the offset
8458 // in img, so, a value between 0 and img_w/h) to the point
8459 // inside image that should be at top left of target area
8460 int transform_x = 0;
8461 int transform_y = 0;
8462 if ( hori_transform == IMG_TRANSFORM_TILE && draw_x ) {
8463 transform_x = (draw_x % img_w);
8464 draw_x = 0;
8465 }
8466 if ( vert_transform == IMG_TRANSFORM_TILE && draw_y ) {
8467 // Strangely, using the following instead of what we did for x/w
8468 // gives the expected result (not investigated, might be
8469 // a bug in LVStretchImgSource::OnLineDecoded() )
8470 transform_y = img_h - (draw_y % img_h);
8471 draw_y = 0;
8472 }
8473 // Ready to have crengine do all the work.
8474 /* Looks like we don't need that:
8475
8476 // (Inspired from LVDocView::drawPageBackground(), we have to do it that complex
8477 // way to avoid memory leaks; and we have to use a 16bpp LVColorDrawBuf,
8478 // 32bpp would mess colors up).
8479 LVRef<LVColorDrawBuf> buf = LVRef<LVColorDrawBuf>( new LVColorDrawBuf(img_w, img_h, 16) );
8480 buf->Draw(img, 0, 0, img_w, img_h, false); // (dither=false doesn't matter with a color buffer)
8481 LVImageSourceRef src = LVCreateDrawBufImageSource(buf.get(), false);
8482 LVImageSourceRef transformed = LVCreateStretchFilledTransform(src, transform_w, transform_h,
8483
8484 We can just transform the original image, which will work in its original
8485 colorspace/depth, ensure alpha/transparency, and will be converted only
8486 at the end to the final drawbuf bit depth.
8487 */
8488 LVImageSourceRef transformed = LVCreateStretchFilledTransform(img, transform_w, transform_h,
8489 hori_transform, vert_transform, transform_x, transform_y);
8490 // We use the DrawBuf clip facility to ensure we don't draw outside this node fmt
8491 lvRect orig_clip;
8492 drawbuf.GetClipRect( &orig_clip ); // Backup the original one
8493 // Set a new one to the target area
8494 lvRect target_clip = lvRect(x0+doc_x, y0+doc_y, x0+doc_x+width, y0+doc_y+height);;
8495 // But don't overflow page top and bottom, in case target spans multiple pages
8496 if ( target_clip.top < orig_clip.top )
8497 target_clip.top = orig_clip.top;
8498 if ( target_clip.bottom > orig_clip.bottom )
8499 target_clip.bottom = orig_clip.bottom;
8500 drawbuf.SetClipRect( &target_clip );
8501 // Draw
8502 drawbuf.Draw(transformed, x0+doc_x+draw_x, y0+doc_y+draw_y, transform_w, transform_h);
8503 drawbuf.SetClipRect( &orig_clip ); // Restore the original one
8504 }
8505 }
8506 }
8507
8508 //=======================================================================
8509 // Draw document
8510 //=======================================================================
8511 // Recursively called as children nodes are walked.
8512 // x0, y0 are coordinates of top left point to draw to in buffer
8513 // dx, dy are width and height to draw to in buffer
8514 // doc_x, doc_y are offset coordinates in document:
8515 // doc_x is initially 0, and doc_y is set to a negative
8516 // value (- page.start) from the y of the top of the page
8517 // (in the whole book height) we want to start showing
DrawDocument(LVDrawBuf & drawbuf,ldomNode * enode,int x0,int y0,int dx,int dy,int doc_x,int doc_y,int page_height,ldomMarkedRangeList * marks,ldomMarkedRangeList * bookmarks,bool draw_content,bool draw_background)8518 void DrawDocument( LVDrawBuf & drawbuf, ldomNode * enode, int x0, int y0, int dx, int dy,
8519 int doc_x, int doc_y, int page_height, ldomMarkedRangeList * marks,
8520 ldomMarkedRangeList *bookmarks, bool draw_content, bool draw_background )
8521 {
8522 // Because of possible floats overflowing their block container box, that could
8523 // be drawn over the area of a next block, we may need to switch to two-steps drawing:
8524 // - first draw only the background of all block nodes and their
8525 // children (excluding floats)
8526 // - then draw the content (border, text, images) without the background, and
8527 // floats (with their background)
8528 // There may still be some issue when main content, some block floats, and some
8529 // embedded floats are mixed and some/all of them have backgrounds...
8530 if ( enode->isElement() )
8531 {
8532 RenderRectAccessor fmt( enode );
8533 doc_x += fmt.getX();
8534 doc_y += fmt.getY();
8535 lvdom_element_render_method rm = enode->getRendMethod();
8536 lUInt32 rend_flags = enode->getDocument()->getRenderBlockRenderingFlags();
8537 // A few things differ when done for TR, THEAD, TBODY and TFOOT
8538 // (erm_table_row_group, erm_table_header_group, erm_table_footer_group, erm_table_row)
8539 bool isTableRowLike = rm >= erm_table_row_group && rm <= erm_table_row;
8540
8541 // Check if this node has content to be shown on viewport
8542 int height = fmt.getHeight();
8543 int top_overflow = fmt.getTopOverflow();
8544 int bottom_overflow = fmt.getBottomOverflow();
8545 if ( (doc_y + height + bottom_overflow <= 0 || doc_y - top_overflow >= 0 + dy) ) {
8546 // We don't have to draw this node.
8547 // Except TR which may have cells with rowspan>1, and even though
8548 // this TR is out of range, it must draw a rowspan>1 cell, so it
8549 // is not empty when a next TR (not out of range) is drawn (this
8550 // makes drawing multi-pages table slow).
8551 if ( !isTableRowLike ) {
8552 return; // out of range
8553 }
8554 if ( BLOCK_RENDERING(rend_flags, ENHANCED) ) {
8555 // But in enhanced mode, we have set bottom overflow on
8556 // TR and table row groups, so we can trust them.
8557 return; // out of range
8558 }
8559 }
8560
8561 int direction = RENDER_RECT_GET_DIRECTION(fmt);
8562 bool is_rtl = direction == REND_DIRECTION_RTL; // shortcut for followup tests
8563
8564 css_style_ref_t style = enode->getStyle();
8565
8566 // Check and draw background
8567 css_length_t bg = style->background_color;
8568 lUInt32 oldColor = 0;
8569 // Don't draw background color for TR and THEAD/TFOOT/TBODY as it could
8570 // override bgcolor of cells with rowspan > 1. We spread, in setNodeStyle(),
8571 // the TR bgcolor to its TDs that must have it, as it should be done (the
8572 // border spacing between cells does not have the bg color of the TR: only
8573 // cells have it).
8574 if ( bg.type==css_val_color && !isTableRowLike ) {
8575 // Even if we don't draw/fill background, we may need to
8576 // drawbuf.SetBackgroundColor() for the text to be correctly
8577 // drawn over this background color
8578 oldColor = drawbuf.GetBackgroundColor();
8579 drawbuf.SetBackgroundColor( bg.value );
8580 if ( draw_background )
8581 drawbuf.FillRect( x0 + doc_x, y0 + doc_y, x0 + doc_x+fmt.getWidth(), y0+doc_y+fmt.getHeight(), bg.value );
8582 }
8583 if ( draw_background && !style->background_image.empty() ) {
8584 if ( enode->getNodeId() == el_body ) {
8585 // CSS specific: <body> background does not obey margin rules
8586 // We don't draw on the fmt width, but on the drawbuf width.
8587 // Also, when in page mode, we'd rather have a fully fixed background,
8588 // (so, not respecting background-repeat and background-position)
8589 // to avoid ghosting and refreshes issues on eInk.
8590 // (This is not easy to ensure when there are multiple <BODY>, with
8591 // possibly different background images, in the viewed page. Drawing
8592 // a second background on the whole drawbuf would override the text
8593 // of the previous body.)
8594 lvRect curclip;
8595 drawbuf.GetClipRect( &curclip );
8596 // Cheap and possibly wrong (if 0-bottom margin and page just fits screen)
8597 // detection of page mode:
8598 bool is_page_mode = curclip.bottom != drawbuf.GetHeight();
8599 if ( is_page_mode && doc_y <= 0 ) {
8600 // If doc_y > 0, we're a BODY starting on this page, and there
8601 // may be a previous one that had already set the background
8602 // image and draw text, that we'd rather not override with
8603 // a new full screen background image.
8604 // Remove the clip, so the background is drawn on the full page
8605 // even on pages with shorter text.
8606 drawbuf.SetClipRect(NULL);
8607 DrawBackgroundImage(enode, drawbuf, 0, 0, 0, 0, drawbuf.GetWidth(), drawbuf.GetHeight());
8608 drawbuf.SetClipRect(&curclip); // restore clip
8609 }
8610 else {
8611 // No fixed background.
8612 // This seems to work fine both in page and scroll modes, with proper
8613 // drawing if multiple body with different backgrounds, with some
8614 // occasional blank white space at DocFragment boundaries.
8615 DrawBackgroundImage(enode, drawbuf, 0, y0, 0, doc_y, drawbuf.GetWidth(), fmt.getHeight());
8616 }
8617 }
8618 else { // regular drawing
8619 DrawBackgroundImage(enode, drawbuf, x0, y0, doc_x, doc_y, fmt.getWidth(), fmt.getHeight());
8620 }
8621 // (Commented identical calls below as they seem redundant with what was just done here)
8622 }
8623 #if (DEBUG_TREE_DRAW!=0)
8624 lUInt32 color;
8625 static lUInt32 const colors2[] = { 0x555555, 0xAAAAAA, 0x555555, 0xAAAAAA, 0x555555, 0xAAAAAA, 0x555555, 0xAAAAAA };
8626 static lUInt32 const colors4[] = { 0x555555, 0xFF4040, 0x40FF40, 0x4040FF, 0xAAAAAA, 0xFF8000, 0xC0C0C0, 0x808080 };
8627 if (drawbuf.GetBitsPerPixel()>=16)
8628 color = colors4[enode->getNodeLevel() & 7];
8629 else
8630 color = colors2[enode->getNodeLevel() & 7];
8631 #endif
8632
8633 int m = enode->getRendMethod();
8634 // We should not get erm_inline, except for inlineBox elements, that
8635 // we must draw as erm_block
8636 if ( m == erm_inline && enode->isBoxingInlineBox() ) {
8637 m = erm_block;
8638 }
8639 switch( m )
8640 {
8641 case erm_table:
8642 case erm_table_row:
8643 case erm_table_row_group:
8644 case erm_table_header_group:
8645 case erm_table_footer_group:
8646 case erm_block:
8647 {
8648 // recursive draw all sub-blocks for blocks
8649 int cnt = enode->getChildCount();
8650
8651 bool in_two_steps_drawing = true;
8652 if ( draw_content && draw_background )
8653 in_two_steps_drawing = false;
8654
8655 if ( in_two_steps_drawing && draw_background ) { // draw_content==false
8656 // Recursively draw background only
8657 for (int i=0; i<cnt; i++) {
8658 ldomNode * child = enode->getChildNode( i );
8659 // No need to draw early the background of floatboxes:
8660 // it will be drawn with the content after non-floating
8661 // content has been drawn below
8662 if ( child->isFloatingBox() )
8663 continue;
8664 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, false, true );
8665 }
8666 // Cleanup and return
8667 if ( bg.type==css_val_color ) {
8668 drawbuf.SetBackgroundColor( oldColor );
8669 }
8670 return;
8671 }
8672
8673 // When here, we are either drawing both content and background, or only content.
8674
8675 // Draw borders before content, so inner content can bleed if necessary on
8676 // the border (some glyphs like 'J' at start or 'f' at end may be drawn
8677 // outside the text content box).
8678 // Don't draw border for TR TBODY... as their borders are never directly
8679 // rendered by Firefox (they are rendered only when border-collapse, when
8680 // they did collapse to the cell, and made out the cell border)
8681 if ( !isTableRowLike )
8682 DrawBorder(enode,drawbuf,x0,y0,doc_x,doc_y,fmt);
8683
8684 // List item marker drawing when css_d_list_item_block and list-style-position = outside
8685 // and list_item_block rendered as block (containing text and block elements)
8686 // Rendering hack (in renderAsListStylePositionInside(): not when text-align "right"
8687 // or "center", we treat it just as "inside", and drawing is managed by renderFinalBlock())
8688 if ( style->display == css_d_list_item_block && !renderAsListStylePositionInside(style, is_rtl) ) {
8689 int width = fmt.getWidth();
8690 int base_width = 0; // for padding_top in %
8691 ldomNode * parent = enode->getParentNode();
8692 if ( parent && !(parent->isNull()) ) {
8693 RenderRectAccessor pfmt( parent );
8694 base_width = pfmt.getWidth();
8695 }
8696 int em = enode->getFont()->getSize();
8697 int padding_top = lengthToPx( style->padding[2], base_width, em ) + measureBorder(enode,0) + DEBUG_TREE_DRAW;
8698 // We already adjusted all children blocks' left-padding and width in renderBlockElement(),
8699 // we just need to draw the marker in the space we made
8700 // But adjust the x to draw our marker if the first line of our
8701 // first final children would start being drawn further because
8702 // some outer floats are involved (as Calibre and Firefox do).
8703 int shift_x = 0;
8704 if ( BLOCK_RENDERING(rend_flags, ENHANCED) ) {
8705 ldomNode * tmpnode = enode;
8706 // Just look at each first descendant for a final child (we may find
8707 // none and would have to look at next children, but well...)
8708 while ( tmpnode && tmpnode->hasChildren() ) {
8709 tmpnode = tmpnode->getChildNode( 0 );
8710 if (tmpnode && tmpnode->getRendMethod() == erm_final) {
8711 RenderRectAccessor tmpfmt( tmpnode );
8712 if ( RENDER_RECT_HAS_FLAG(tmpfmt, INNER_FIELDS_SET) ) {
8713 int inner_width = tmpfmt.getInnerWidth();
8714 BlockFloatFootprint float_footprint;
8715 float_footprint.restore( tmpnode, inner_width );
8716 shift_x = float_footprint.getTopShiftX(inner_width, is_rtl);
8717 }
8718 break;
8719 }
8720 }
8721 }
8722
8723 LFormattedTextRef txform( enode->getDocument()->createFormattedText() );
8724 // If RTL, have the marker aligned to the right inside list_marker_width
8725 lUInt32 txt_flags = is_rtl ? LTEXT_ALIGN_RIGHT : 0;
8726 int list_marker_width;
8727 lString32 marker = renderListItemMarker( enode, list_marker_width, txform.get(), -1, txt_flags);
8728 /*
8729 lUInt32 h = txform->Format( (lUInt16)list_marker_width, (lUInt16)page_height, direction );
8730 lvRect clip;
8731 drawbuf.GetClipRect( &clip );
8732 if (doc_y + y0 + h <= clip.bottom) { // draw only if marker fully fits on page
8733 */
8734 // Better to draw it, even if it slightly overflows, or we might lose some
8735 // list item number for no real reason
8736 txform->Format( (lUInt16)list_marker_width, (lUInt16)page_height, direction );
8737 // In both LTR and RTL, for erm_block, we draw the marker inside 'width',
8738 // (only the child elements got their width shrinked by list_marker_width).
8739 if ( is_rtl ) {
8740 txform->Draw( &drawbuf, doc_x+x0 + width + shift_x - list_marker_width, doc_y+y0 + padding_top );
8741 }
8742 else {
8743 // (Don't shift by padding left, the list marker is outside padding left)
8744 txform->Draw( &drawbuf, doc_x+x0 + shift_x, doc_y+y0 + padding_top );
8745 }
8746 }
8747
8748 // Draw first the non-floating nodes (as their background color would
8749 // otherwise be drawn over any floating node content drawn previously
8750 // (But if floats or their children have themselves some background,
8751 // and negative margins are involved, their background could be drawn
8752 // over non-floating text... but that's not easy to check...)
8753 bool has_floats = false;
8754 for (int i=0; i<cnt; i++) {
8755 ldomNode * child = enode->getChildNode( i );
8756 if ( child->isFloatingBox() ) {
8757 has_floats = true;
8758 // Floats can be drawn after non-floats no matter
8759 // if we went two-steps or not.
8760 continue;
8761 }
8762
8763 if ( in_two_steps_drawing ) {
8764 // If we are already in 2-steps drawing, drawing background
8765 // first and then content is already taken care of by some
8766 // upper node. So, no need to check if we need to switch,
8767 // just draw the content.
8768 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, true, false );
8769 continue;
8770 }
8771
8772 // If not yet in 2-steps drawing, we need to check if we
8773 // have to do that 2-steps drawing ourselves.
8774 RenderRectAccessor cfmt( child );
8775 if ( cfmt.getBottomOverflow() == 0 ) {
8776 // No bottom overflow: just draw both content and background
8777 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, true, true );
8778 continue;
8779 }
8780
8781 // This child has content that overflows: we need to 2-steps draw
8782 // it and its siblings up until all overflow is passed.
8783 // printf("Starting 2-steps drawing at %d %s\n", cfmt.getY(),
8784 // UnicodeToLocal(ldomXPointer(child, 0).toString()).c_str());
8785 int overflow_y = cfmt.getY() + cfmt.getHeight() + cfmt.getBottomOverflow();
8786 int last_two_steps_drawn_node;
8787 for (int j=i; j<cnt; j++) {
8788 last_two_steps_drawn_node = j;
8789 child = enode->getChildNode( j );
8790 if ( child->isFloatingBox() ) {
8791 has_floats = true;
8792 continue;
8793 }
8794 // Draw backgrounds (recusively)
8795 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, false, true );
8796 cfmt = RenderRectAccessor( child );
8797 int current_y = cfmt.getY() + cfmt.getHeight();
8798 int this_overflow = cfmt.getBottomOverflow();
8799 if ( current_y >= overflow_y && this_overflow == 0 ) {
8800 // Overflow y passed by, and no more new overflow, we
8801 // can switch back to 1-step drawing
8802 // printf("Done with 2-steps drawing after %d %s\n", current_y,
8803 // UnicodeToLocal(ldomXPointer(child, 0).toString()).c_str());
8804 break;
8805 }
8806 overflow_y = current_y + this_overflow;
8807 }
8808 // Now, draw the content of all these nodes we've just drawn the background of
8809 for (int k=i; k<=last_two_steps_drawn_node; k++) {
8810 child = enode->getChildNode( k );
8811 if ( child->isFloatingBox() ) {
8812 has_floats = true;
8813 continue;
8814 }
8815 // Draw contents (recursively)
8816 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, true, false );
8817 }
8818 // Go on with 1-step drawing
8819 i = last_two_steps_drawn_node;
8820 }
8821
8822 // Then draw over the floating nodes ignored in previous loop
8823 if (has_floats) {
8824 for (int i=0; i<cnt; i++) {
8825 ldomNode * child = enode->getChildNode( i );
8826 if ( !child->isFloatingBox() )
8827 continue;
8828 // Draw both content and background for floats
8829 DrawDocument( drawbuf, child, x0, y0, dx, dy, doc_x, doc_y, page_height, marks, bookmarks, true, true );
8830 }
8831 }
8832
8833 #if (DEBUG_TREE_DRAW!=0)
8834 drawbuf.FillRect( doc_x+x0, doc_y+y0, doc_x+x0+fmt.getWidth(), doc_y+y0+1, color );
8835 drawbuf.FillRect( doc_x+x0, doc_y+y0, doc_x+x0+1, doc_y+y0+fmt.getHeight(), color );
8836 drawbuf.FillRect( doc_x+x0+fmt.getWidth()-1, doc_y+y0, doc_x+x0+fmt.getWidth(), doc_y+y0+fmt.getHeight(), color );
8837 drawbuf.FillRect( doc_x+x0, doc_y+y0+fmt.getHeight()-1, doc_x+x0+fmt.getWidth(), doc_y+y0+fmt.getHeight(), color );
8838 #endif
8839 // Border was previously drawn here, but has been moved above for earlier drawing.
8840 }
8841 break;
8842 case erm_final:
8843 {
8844 // No sub-background drawing for erm_final (its background was
8845 // drawn above, before the switch())
8846 if ( !draw_content ) {
8847 // Cleanup and return
8848 if ( bg.type==css_val_color ) {
8849 drawbuf.SetBackgroundColor( oldColor );
8850 }
8851 return;
8852 }
8853
8854 // Draw borders before content, so inner content can bleed if necessary on
8855 // the border (some glyphs like 'J' at start or 'f' at end may be drawn
8856 // outside the text content box).
8857 DrawBorder(enode, drawbuf, x0, y0, doc_x, doc_y, fmt);
8858
8859 // Get ready to create a LFormattedText with the correct content width
8860 // and position: we'll have it draw itself at the right coordinates.
8861 int width = fmt.getWidth();
8862 int inner_width;
8863 int padding_left;
8864 int padding_top;
8865 if ( RENDER_RECT_HAS_FLAG(fmt, INNER_FIELDS_SET) ) { // enhanced rendering for erm_final nodes
8866 // This flag is set only when in enhanced rendering mode, and only on erm_final nodes.
8867 padding_left = fmt.getInnerX();
8868 padding_top = fmt.getInnerY();
8869 inner_width = fmt.getInnerWidth();
8870 }
8871 else { // legacy rendering
8872 // Note: this computation is wrong for paddings in %, as they should
8873 // apply against the parent container width, not this block width.
8874 int em = enode->getFont()->getSize();
8875 bool draw_padding_bg = true; //( enode->getRendMethod()==erm_final );
8876 padding_left = !draw_padding_bg ? 0 : lengthToPx( style->padding[0], width, em ) + DEBUG_TREE_DRAW+measureBorder(enode,3);
8877 int padding_right = !draw_padding_bg ? 0 : lengthToPx( style->padding[1], width, em ) + DEBUG_TREE_DRAW+measureBorder(enode,1);
8878 padding_top = !draw_padding_bg ? 0 : lengthToPx( style->padding[2], width, em ) + DEBUG_TREE_DRAW+measureBorder(enode,0);
8879 inner_width = width - padding_left - padding_right;
8880 }
8881
8882 // List item marker drawing when css_d_list_item_block and list-style-position = outside
8883 // and list_item_block rendered as final (containing only text and inline elements)
8884 // Rendering hack (in renderAsListStylePositionInside(): not when text-align "right"
8885 // or "center", we treat it just as "inside", and drawing is managed by renderFinalBlock())
8886 if ( style->display == css_d_list_item_block && !renderAsListStylePositionInside(style, is_rtl) ) {
8887 // We already adjusted our block X and width in renderBlockElement(),
8888 // we just need to draw the marker in the space we made on the left of
8889 // this node.
8890 // But adjust the x to draw our marker if the first line of this
8891 // final block would start being drawn further because some outer
8892 // floats are involved (as Calibre and Firefox do).
8893 BlockFloatFootprint float_footprint;
8894 float_footprint.restore( enode, inner_width );
8895 int shift_x = float_footprint.getTopShiftX(inner_width, is_rtl);
8896
8897 LFormattedTextRef txform( enode->getDocument()->createFormattedText() );
8898
8899 // If RTL, have the marker aligned to the right inside list_marker_width
8900 lUInt32 txt_flags = is_rtl ? LTEXT_ALIGN_RIGHT : 0;
8901 int list_marker_width;
8902 lString32 marker = renderListItemMarker( enode, list_marker_width, txform.get(), -1, txt_flags);
8903 /*
8904 lUInt32 h = txform->Format( (lUInt16)list_marker_width, (lUInt16)page_height, direction );
8905 lvRect clip;
8906 drawbuf.GetClipRect( &clip );
8907 if (doc_y + y0 + h <= clip.bottom) { // draw only if marker fully fits on page
8908 */
8909 // Better to draw it, even if it slightly overflows, or we might lose some
8910 // list item number for no real reason
8911 txform->Format( (lUInt16)list_marker_width, (lUInt16)page_height, direction );
8912 // In both LTR and RTL, for erm_final, we draw the marker outside 'width',
8913 // as 'width' has already been shrinked by list_marker_width.
8914 if ( is_rtl ) {
8915 txform->Draw( &drawbuf, doc_x+x0 + width + shift_x, doc_y+y0 + padding_top, NULL, NULL );
8916 }
8917 else {
8918 txform->Draw( &drawbuf, doc_x+x0 + shift_x - list_marker_width, doc_y+y0 + padding_top, NULL, NULL );
8919 }
8920 // Note: if there's a float on the left of the list item, we let
8921 // the marker where it would be if there were no float, while Firefox
8922 // would shift it by the float width. But for both, the marker may
8923 // be hidden by/shown inside the float...
8924 // Not obvious to do as Firefox, which draws it like if it has some
8925 // negative padding from the text.
8926 }
8927
8928 // draw whole node content as single formatted object
8929 LFormattedTextRef txform;
8930 enode->renderFinalBlock( txform, &fmt, inner_width );
8931 fmt.push();
8932 {
8933 lvRect rc;
8934 enode->getAbsRect( rc, true );
8935 if ( !RENDER_RECT_HAS_FLAG(fmt, INNER_FIELDS_SET) ) {
8936 // In legacy mode, getAbsRect( ..., inner=true) did not have
8937 // the inner geometry stored in fmt and computed. We need
8938 // to correct it with paddings:
8939 int em = enode->getFont()->getSize();
8940 int padding_left = measureBorder(enode,3)+lengthToPx(enode->getStyle()->padding[0],rc.width(),em);
8941 int padding_right = measureBorder(enode,1)+lengthToPx(enode->getStyle()->padding[1],rc.width(),em);
8942 int padding_top = measureBorder(enode,0)+lengthToPx(enode->getStyle()->padding[2],rc.height(),em);
8943 int padding_bottom = measureBorder(enode,2)+lengthToPx(enode->getStyle()->padding[3],rc.height(),em);
8944 rc.top += padding_top;
8945 rc.left += padding_left;
8946 rc.right -= padding_right;
8947 rc.bottom -= padding_bottom;
8948 }
8949 ldomMarkedRangeList *nbookmarks = NULL;
8950 if ( bookmarks && bookmarks->length()) { // internal crengine bookmarked text highlights
8951 nbookmarks = new ldomMarkedRangeList( bookmarks, rc );
8952 }
8953 if ( marks && marks->length() ) { // "native highlighting" of a selection in progress
8954 // Keep marks that are part of the top and bottom overflows
8955 lvRect crop_rc = lvRect(rc);
8956 crop_rc.top -= fmt.getTopOverflow();
8957 crop_rc.bottom += fmt.getBottomOverflow();
8958 ldomMarkedRangeList nmarks( marks, rc, &crop_rc );
8959 // DrawBackgroundImage(enode, drawbuf, x0, y0, doc_x, doc_y, fmt.getWidth(), fmt.getHeight());
8960 // Draw regular text with currently marked highlights
8961 txform->Draw( &drawbuf, doc_x+x0 + padding_left, doc_y+y0 + padding_top, &nmarks, nbookmarks );
8962 } else {
8963 // DrawBackgroundImage(enode, drawbuf, x0, y0, doc_x, doc_y, fmt.getWidth(), fmt.getHeight());
8964 // Draw regular text, no marks
8965 txform->Draw( &drawbuf, doc_x+x0 + padding_left, doc_y+y0 + padding_top, marks, nbookmarks );
8966 }
8967 if (nbookmarks)
8968 delete nbookmarks;
8969 }
8970 #if (DEBUG_TREE_DRAW!=0)
8971 drawbuf.FillRect( doc_x+x0, doc_y+y0, doc_x+x0+fmt.getWidth(), doc_y+y0+1, color );
8972 drawbuf.FillRect( doc_x+x0, doc_y+y0, doc_x+x0+1, doc_y+y0+fmt.getHeight(), color );
8973 drawbuf.FillRect( doc_x+x0+fmt.getWidth()-1, doc_y+y0, doc_x+x0+fmt.getWidth(), doc_y+y0+fmt.getHeight(), color );
8974 drawbuf.FillRect( doc_x+x0, doc_y+y0+fmt.getHeight()-1, doc_x+x0+fmt.getWidth(), doc_y+y0+fmt.getHeight(), color );
8975 #endif
8976 // Border was previously drawn here, but has been moved above for earlier drawing.
8977 }
8978 break;
8979 case erm_invisible:
8980 // don't draw invisible blocks
8981 break;
8982 case erm_killed:
8983 if ( !draw_content ) {
8984 if ( bg.type==css_val_color ) {
8985 drawbuf.SetBackgroundColor( oldColor );
8986 }
8987 return;
8988 }
8989 //drawbuf.FillRect( x0 + doc_x, y0 + doc_y, x0 + doc_x+fmt.getWidth(), y0+doc_y+fmt.getHeight(), 0xFF0000 );
8990 // Draw something that does not look like a bullet
8991 // This should render in red something like: [\]
8992 drawbuf.RoundRect( x0 + doc_x, y0 + doc_y, x0 + doc_x+fmt.getWidth(), y0+doc_y+fmt.getHeight(),
8993 fmt.getWidth()/4, fmt.getWidth()/4, 0xFF0000, 0x9 );
8994 drawbuf.FillRect( x0 + doc_x + fmt.getWidth()/6, y0 + doc_y + fmt.getHeight()*3/8,
8995 x0 + doc_x + fmt.getWidth()*5/6, y0+doc_y+fmt.getHeight()*5/8, 0xFF0000 );
8996 break;
8997 default:
8998 break;
8999 //crFatalError(); // error
9000 }
9001 if ( bg.type==css_val_color ) {
9002 drawbuf.SetBackgroundColor( oldColor );
9003 }
9004 }
9005 }
9006
9007 /* Not used anywhere: not updated for absolute length units and *256
9008 void convertLengthToPx( css_length_t & val, int base_px, int base_em )
9009 {
9010 switch( val.type )
9011 {
9012 case css_val_inherited:
9013 val = css_length_t ( base_px );
9014 break;
9015 case css_val_px:
9016 // nothing to do
9017 break;
9018 case css_val_ex: // not implemented: treat as em
9019 case css_val_em: // value = em*256
9020 val = css_length_t ( (base_em * val.value) >> 8 );
9021 break;
9022 case css_val_percent:
9023 val = css_length_t ( (base_px * val.value) / 100 );
9024 break;
9025 case css_val_unspecified:
9026 case css_val_in: // 2.54 cm
9027 case css_val_cm:
9028 case css_val_mm:
9029 case css_val_pt: // 1/72 in
9030 case css_val_pc: // 12 pt
9031 case css_val_color:
9032 // not supported: use inherited value
9033 val = css_length_t ( val.value );
9034 break;
9035 }
9036 }
9037 */
9038
spreadParent(css_length_t & val,css_length_t & parent_val,bool unspecified_is_inherited=true)9039 inline void spreadParent( css_length_t & val, css_length_t & parent_val, bool unspecified_is_inherited=true )
9040 {
9041 if ( val.type == css_val_inherited || (val.type == css_val_unspecified && unspecified_is_inherited) )
9042 val = parent_val;
9043 }
9044
setNodeStyle(ldomNode * enode,css_style_ref_t parent_style,LVFontRef parent_font)9045 void setNodeStyle( ldomNode * enode, css_style_ref_t parent_style, LVFontRef parent_font )
9046 {
9047 CR_UNUSED(parent_font);
9048 //lvdomElementFormatRec * fmt = node->getRenderData();
9049 css_style_ref_t style( new css_style_rec_t );
9050 css_style_rec_t * pstyle = style.get();
9051
9052 lUInt16 nodeElementId = enode->getNodeId();
9053 ldomDocument * doc = enode->getDocument();
9054 lUInt32 domVersionRequested = doc->getDOMVersionRequested();
9055
9056 if (domVersionRequested < 20180524) {
9057 // The display property initial value has been changed from css_d_inherit
9058 // to css_d_inline (as per spec, and so that an unknown element does not
9059 // become block when contained in a P, and inline when contained in a SPAN)
9060 pstyle->display = css_d_inherit;
9061 }
9062
9063 // if ( parent_style.isNull() ) {
9064 // CRLog::error("parent style is null!!!");
9065 // }
9066
9067 // init default style attribute values
9068 const css_elem_def_props_t * type_ptr = enode->getElementTypePtr();
9069 bool is_object = false;
9070 if (type_ptr) {
9071 is_object = type_ptr->is_object;
9072 pstyle->display = type_ptr->display;
9073 pstyle->white_space = type_ptr->white_space;
9074
9075 // Account for backward incompatible changes in fb2def.h
9076 if (domVersionRequested < 20200824) { // revert what was changed 20200824
9077 if (nodeElementId >= el_details && nodeElementId <= el_wbr) { // newly added block elements
9078 pstyle->display = css_d_inline; // previously unknown and shown as inline
9079 if (domVersionRequested < 20180524) {
9080 pstyle->display = css_d_inherit; // previously unknown and display: inherit
9081 }
9082 }
9083 if (domVersionRequested < 20180528) { // revert what was changed 20180528
9084 if (nodeElementId == el_form) {
9085 pstyle->display = css_d_none; // otherwise shown as block, as it may have textual content
9086 }
9087 if (nodeElementId == el_code) {
9088 pstyle->white_space = css_ws_pre; // otherwise white-space: normal, as browsers do
9089 }
9090 if (nodeElementId >= el_address && nodeElementId <= el_xmp) { // newly added block elements
9091 pstyle->display = css_d_inline; // previously unknown and shown as inline
9092 if (domVersionRequested < 20180524) {
9093 pstyle->display = css_d_inherit; // previously unknown and display: inherit
9094 }
9095 }
9096 if (domVersionRequested < 20180524) { // revert what was fixed 20180524
9097 if (nodeElementId == el_cite) {
9098 pstyle->display = css_d_block; // otherwise correctly set to css_d_inline
9099 }
9100 if (nodeElementId == el_li) {
9101 pstyle->display = css_d_list_item_legacy; // otherwise correctly set to css_d_list_item_block
9102 }
9103 if (nodeElementId == el_style) {
9104 pstyle->display = css_d_inline; // otherwise correctly set to css_d_none (hidden)
9105 }
9106 }
9107 }
9108 }
9109 }
9110
9111 // Firefox resets text-align: to 'left' for table (eg: <center><table>
9112 // doesn't have its cells' content centered, not even justified if body
9113 // has "text-align: justify"), while crengine would make them centered.
9114 // So, we dont wan't table to starts with css_ta_inherit. We could use
9115 // css_ta_left (as Firefox), but it's best in our context to use the
9116 // value set to the (or current DocFragment's) BODY node, which starts
9117 // with css_ta_left but may be set to css_ta_justify by our epub.css.
9118 if (nodeElementId == el_table) {
9119 // To do as Firefox:
9120 // pstyle->text_align = css_ta_left;
9121 // But we'd rather use the BODY value:
9122 ldomNode * body = enode->getParentNode();
9123 while ( body != NULL && body->getNodeId()!=el_body )
9124 body = body->getParentNode();
9125 if ( body ) {
9126 pstyle->text_align = body->getStyle()->text_align;
9127 }
9128 }
9129
9130 if (enode->getNodeNsId() == ns_epub) {
9131 if (nodeElementId == el_case) { // <epub:case required-namespace="...">
9132 // As we don't support any specific namespace (like MathML, SVG...), just
9133 // hide <epub:case> content - it must be followed by a <epub:default>
9134 // section with usually regular content like some image.
9135 ldomNode * parent = enode->getParentNode();
9136 if (parent && parent->getNodeNsId() == ns_epub && parent->getNodeId() == el_switch) {
9137 // (We can't here check parent's other children for the presence of one
9138 // el_default, as we can be called while XML is being parsed and the DOM
9139 // built and siblings not yet there, so just trust there is an el_default.)
9140 pstyle->display = css_d_none;
9141 }
9142 }
9143 }
9144
9145 // display before stylesheet is applied (for fallback below if legacy mode)
9146 css_display_t orig_elem_display = pstyle->display;
9147
9148 // not used (could be used for 'rem', but we have it in gRootFontSize)
9149 // int baseFontSize = enode->getDocument()->getDefaultFont()->getSize();
9150
9151 //////////////////////////////////////////////////////
9152 // apply style sheet
9153 //////////////////////////////////////////////////////
9154 doc->applyStyle( enode, pstyle );
9155
9156 //////////////////////////////////////////////////////
9157 // apply node style= attribute
9158 //////////////////////////////////////////////////////
9159 if ( doc->getDocFlag(DOC_FLAG_ENABLE_INTERNAL_STYLES) && enode->hasAttribute( LXML_NS_ANY, attr_style ) ) {
9160 lString32 nodeStyle = enode->getAttributeValue( LXML_NS_ANY, attr_style );
9161 if ( !nodeStyle.empty() ) {
9162 nodeStyle = cs32("{") + nodeStyle + "}";
9163 LVCssDeclaration decl;
9164 lString8 s8 = UnicodeToUtf8(nodeStyle);
9165 const char * s = s8.c_str();
9166 // We can't get the codeBase of this node anymore at this point, which
9167 // would be needed to resolve "background-image: url(...)" relative
9168 // file path... So these won't work when defined in a style= attribute.
9169 if ( decl.parse( s, domVersionRequested, false, doc ) ) {
9170 decl.apply( pstyle );
9171 }
9172 }
9173 }
9174
9175 // As per-specs (and to avoid checking edge cases in initNodeRendMethod()):
9176 // https://www.w3.org/TR/css-tables-3/#table-structure
9177 // "Authors should not assign a display type from the previous
9178 // list [inline-table & table*] to replaced elements (eg: input
9179 // fields or images). When the display property of a replaced
9180 // element computes to one of these values, it is handled
9181 // instead as though the author had declared either block
9182 // (for table display) or inline (for all other values).
9183 // Also:
9184 // "This is a breaking change from css 2.1 but matches implementations."
9185 // The fallback values was different per-browser, as seen in:
9186 // https://github.com/w3c/csswg-drafts/issues/508
9187 // but the discussion resolved it to:
9188 // - All internal 'table-*' displays on replaced elements behave as 'inline'.
9189 // - 'table' falls back to 'block', 'inline-table' falls back to 'inline'
9190 //
9191 // Note that with this bogus HTML snippet:
9192 // <table style="border: solid 1px black">
9193 // <img src="some.png" style="display: table-cell"/>
9194 // <tr><img src="some.png" style="display: table-cell"/><td>text</td></tr>
9195 // <tr><td>text in table cell</td><td>text</td></tr>
9196 // </table
9197 // Firefox would draw both images before/outside of the table border
9198 // (so, making them inline and moving them outside the table),
9199 // while we will keep them inline inside the table, and wrapped
9200 // into tabularBoxes acting as the missing table elements.
9201 if ( is_object ) {
9202 switch ( pstyle->display ) {
9203 case css_d_table:
9204 pstyle->display = css_d_block;
9205 break;
9206 case css_d_table_row_group:
9207 case css_d_table_header_group:
9208 case css_d_table_footer_group:
9209 case css_d_table_row:
9210 case css_d_table_column_group:
9211 case css_d_table_column:
9212 case css_d_table_cell:
9213 case css_d_table_caption:
9214 case css_d_inline_table:
9215 pstyle->display = css_d_inline;
9216 break;
9217 default:
9218 break;
9219 }
9220 }
9221
9222 // <br/> can be set to "display: block" by publishers, but
9223 // Firefox and Calibre do not handle them like other block
9224 // elements: they won't ensure a "height:" set on them.
9225 // It's not clear how such BR should be handled, but comparing
9226 // with how Firefox/Calibre/Chrome render them, it looks
9227 // like we'll render quite as they do when forcing BR to
9228 // always be css_d_inline:
9229 // When met alongside block elements, they'll be autoboxed and
9230 // will ensure just their (possibly inherited) line-height.
9231 if (nodeElementId == el_br && pstyle->display != css_d_none) {
9232 pstyle->display = css_d_inline;
9233 }
9234
9235 // Ensure any <stylesheet> element (that crengine "added BODY>stylesheet child
9236 // element with HEAD>STYLE&LINKS content") stays invisible (it could end up being
9237 // made visible when some book stylesheet contains "body > * {display: block;}")
9238 if (nodeElementId == el_stylesheet) {
9239 pstyle->display = css_d_none;
9240 }
9241
9242 lUInt32 rend_flags = doc->getRenderBlockRenderingFlags();
9243 if ( BLOCK_RENDERING(rend_flags, PREPARE_FLOATBOXES) ) {
9244 // https://developer.mozilla.org/en-US/docs/Web/CSS/float
9245 // As float implies the use of the block layout, it modifies the computed value
9246 // of the display values, in some cases: [...]
9247 // Mostly everything becomes display: block
9248 // As we use tests like node->getStyle()->display == css_d_block in a few places,
9249 // it's easier to change it here than add tests for getStyle()->float_ in these
9250 // places.
9251 // (This may not have much effect, as it may get ignored in initNodeRendMethod()
9252 // when !FLOAT_FLOATBOXES)
9253 // At the time setNodeStyle() is called, this can only happen on an original
9254 // element with float:, and not on a wrapping floatBox element which is either
9255 // not there yet (or just added, which will be handled by next 'if'), or has
9256 // not yet got its float_ from its child. So the ->display of the floatBox
9257 // element will have to be updated too elsewhere.
9258 if ( pstyle->float_ == css_f_left || pstyle->float_ == css_f_right ) {
9259 if ( pstyle->display <= css_d_inline ) {
9260 pstyle->display = css_d_block;
9261 }
9262 }
9263 }
9264 if ( BLOCK_RENDERING(rend_flags, WRAP_FLOATS) ) {
9265 if ( nodeElementId == el_floatBox ) {
9266 // floatBox added, by initNodeRendMethod(), as a wrapper around
9267 // element with float:.
9268 // We want to set the floatBox->style->float_ to the same value
9269 // as the wrapped original node.
9270 // We are either called explicitely (by initNodeRendMethod) while
9271 // the XML is being loaded, where the el_floatBox has just been
9272 // created - or on re-rendering when the el_floatBox is already there.
9273 // In the XML loading case, the child styles have already been applied,
9274 // so we can trust the child properties.
9275 // In the re-rendering case, the child styles have been reset and have
9276 // not yet been computed, so we can't apply it. This will be fixed
9277 // by initNodeRendMethod() when processing the nodes deep-first and
9278 // up the DOM tree, after the styles have been applied.
9279 if (enode->getChildCount() == 1) {
9280 ldomNode * child = enode->getChildNode(0);
9281 css_style_ref_t child_style = child->getStyle();
9282 if ( ! child_style.isNull() ) { // Initial XML loading phase
9283 // This child_style is only non-null on the initial XML loading.
9284 // We do as in ldomNode::initNodeRendMethod() when the floatBox
9285 // is already there (on re-renderings):
9286 pstyle->float_ = child_style->float_;
9287 if (child_style->display <= css_d_inline) { // when !PREPARE_FLOATBOXES
9288 pstyle->display = css_d_inline; // become an inline wrapper
9289 }
9290 else if (child_style->display == css_d_none) {
9291 pstyle->display = css_d_none; // stay invisible
9292 }
9293 else { // everything else (including tables) must be wrapped by a block
9294 pstyle->display = css_d_block;
9295 }
9296 }
9297 // Else (child_style is null), it's a re-rendering: nothing special
9298 // to do, this will be dealt with later by initNodeRendMethod().
9299 }
9300 }
9301 }
9302 else { // legacy rendering or enhanced with no float support
9303 // Cancel any float value set from stylesheets:
9304 // this should be enough to trigger a displayhash mismatch
9305 // and a popup inviting the user to reload, to get rid of
9306 // floatBox elements.
9307 pstyle->float_ = css_f_none;
9308 }
9309
9310 if ( BLOCK_RENDERING(rend_flags, BOX_INLINE_BLOCKS) ) {
9311 // See above, same reasoning
9312 if ( nodeElementId == el_inlineBox ) {
9313 // el_inlineBox are "display: inline" by default (defined in fb2def.h)
9314 if (enode->getChildCount() == 1) {
9315 ldomNode * child = enode->getChildNode(0);
9316 css_style_ref_t child_style = child->getStyle();
9317 if ( ! child_style.isNull() ) { // Initial XML loading phase
9318 // This child_style is only non-null on the initial XML loading.
9319 // We do as in ldomNode::initNodeRendMethod() when the inlineBox
9320 // is already there (on re-renderings):
9321 // (If this is an inlineBox in the initial XML loading phase,
9322 // child is necessarily css_d_inline_block or css_d_inline_table,
9323 // or this node is <inlineBox T=EmbeddedBlock>.
9324 // The following 'else's should never trigger.
9325 if (child_style->display == css_d_inline_block || child_style->display == css_d_inline_table) {
9326 pstyle->display = css_d_inline; // become an inline wrapper
9327 pstyle->vertical_align = child_style->vertical_align;
9328 }
9329 else if ( enode->hasAttribute( attr_T ) ) { // T="EmbeddedBlock"
9330 // (no other possible value yet, no need to compare strings)
9331 pstyle->display = css_d_inline; // wrap bogus "block among inlines" in inline
9332 }
9333 else if (child_style->display <= css_d_inline) {
9334 pstyle->display = css_d_inline; // wrap inline in inline
9335 }
9336 else if (child_style->display == css_d_none) {
9337 pstyle->display = css_d_none; // stay invisible
9338 }
9339 else { // everything else must be wrapped by a block
9340 pstyle->display = css_d_block;
9341 }
9342 }
9343 // Else (child_style is null), it's a re-rendering: nothing special
9344 // to do, this will be dealt with later by initNodeRendMethod().
9345 }
9346 }
9347 }
9348 else {
9349 // Legacy rendering or enhanced with no inline-block support
9350 // Fallback to the default style for the element
9351 // (before enhanced rendering, css_d_inline_block did not exist, so the
9352 // node probably stayed with the default display: of the element when
9353 // no other lower specificity CSS set another).
9354 if ( pstyle->display == css_d_inline_block || pstyle->display == css_d_inline_table ) {
9355 if ( !BLOCK_RENDERING(rend_flags, ENHANCED) && pstyle->display == css_d_inline_table ) {
9356 // In legacy mode, inline-table was handled like css_d_block (as all
9357 // not specifically handled css_d_* are, so probably unwillingly).
9358 pstyle->display = css_d_block;
9359 }
9360 else {
9361 pstyle->display = orig_elem_display;
9362 }
9363 }
9364 }
9365
9366 // Avoid some new features when migration to normalized xpointers has not yet been done
9367 if ( domVersionRequested < DOM_VERSION_WITH_NORMALIZED_XPOINTERS ) {
9368 // display: ruby may wrap the element content in many inlineBox/rubyBox.
9369 // Avoid that until migrated to normalized xpointers by handling
9370 // them as css_d_inline like before ruby support.
9371 if ( pstyle->display == css_d_ruby ) {
9372 pstyle->display = css_d_inline;
9373 }
9374 }
9375
9376 // update inherited style attributes
9377 /*
9378 #define UPDATE_STYLE_FIELD(fld,inherit_value) \
9379 if (pstyle->fld == inherit_value) \
9380 pstyle->fld = parent_style->fld
9381 */
9382 #define UPDATE_STYLE_FIELD(fld,inherit_value) \
9383 if (pstyle->fld == inherit_value) \
9384 pstyle->fld = parent_style->fld
9385 #define UPDATE_LEN_FIELD(fld) \
9386 switch( pstyle->fld.type ) \
9387 { \
9388 case css_val_inherited: \
9389 pstyle->fld = parent_style->fld; \
9390 break; \
9391 /* relative values to parent style */\
9392 case css_val_percent: \
9393 pstyle->fld.type = parent_style->fld.type; \
9394 pstyle->fld.value = parent_style->fld.value * pstyle->fld.value / 100 / 256; \
9395 break; \
9396 case css_val_em: \
9397 pstyle->fld.type = parent_style->fld.type; \
9398 pstyle->fld.value = parent_style->font_size.value * pstyle->fld.value / 256; \
9399 break; \
9400 case css_val_ex: \
9401 pstyle->fld.type = parent_style->fld.type; \
9402 pstyle->fld.value = parent_style->font_size.value * pstyle->fld.value / 512; \
9403 break; \
9404 default: \
9405 /* absolute values, no need to relate to parent style */\
9406 break; \
9407 }
9408
9409 //if ( (pstyle->display == css_d_inline) && (pstyle->text_align==css_ta_inherit))
9410 //{
9411 //if (parent_style->text_align==css_ta_inherit)
9412 //parent_style->text_align = css_ta_center;
9413 //}
9414
9415 if (domVersionRequested < 20180524) { // display should not be inherited
9416 UPDATE_STYLE_FIELD( display, css_d_inherit );
9417 }
9418 UPDATE_STYLE_FIELD( white_space, css_ws_inherit );
9419 UPDATE_STYLE_FIELD( text_align, css_ta_inherit );
9420 UPDATE_STYLE_FIELD( text_align_last, css_ta_inherit );
9421 UPDATE_STYLE_FIELD( text_decoration, css_td_inherit );
9422 UPDATE_STYLE_FIELD( text_transform, css_tt_inherit );
9423 UPDATE_STYLE_FIELD( hyphenate, css_hyph_inherit );
9424 UPDATE_STYLE_FIELD( orphans, css_orphans_widows_inherit );
9425 UPDATE_STYLE_FIELD( widows, css_orphans_widows_inherit );
9426 UPDATE_STYLE_FIELD( list_style_type, css_lst_inherit );
9427 UPDATE_STYLE_FIELD( list_style_position, css_lsp_inherit );
9428
9429 // Note: we don't inherit "direction" (which should be inherited per specs);
9430 // We'll handle inheritance of direction in renderBlockEnhanced, because
9431 // it is also specified, with higher priority, by dir= attributes.
9432
9433 // page_break_* should not be inherited per CSS specs, and as we set
9434 // each node to start with css_pb_auto, they shouldn't get a chance
9435 // to be inherited, except if a publisher really uses 'inherit'.
9436 // We usually don't ensure inheritance of styles that are not inherited
9437 // by default, but as these were originally in the code, let's keep them.
9438 UPDATE_STYLE_FIELD( page_break_before, css_pb_inherit );
9439 UPDATE_STYLE_FIELD( page_break_after, css_pb_inherit );
9440 UPDATE_STYLE_FIELD( page_break_inside, css_pb_inherit );
9441
9442 // vertical_align
9443 // Should not be inherited per CSS specs: we fixed its propagation
9444 // to children with the use of 'valign_dy'.
9445 // But our default value for each node is not "inherit" but "baseline",
9446 // so we should be fine allowing an explicite "inherit" to get its parent
9447 // value. This is actually required with html5.css where TR,TD,TH are
9448 // explicitely set to "vertical-align: inherit", so they can inherit
9449 // from "thead, tbody, tfoot, table > tr { vertical-align: middle}"
9450 if ( pstyle->vertical_align.type == css_val_unspecified && pstyle->vertical_align.value == css_va_inherit)
9451 pstyle->vertical_align = parent_style->vertical_align;
9452
9453 UPDATE_STYLE_FIELD( font_style, css_fs_inherit );
9454 UPDATE_STYLE_FIELD( font_weight, css_fw_inherit );
9455 if ( pstyle->font_family == css_ff_inherit ) {
9456 //UPDATE_STYLE_FIELD( font_name, "" );
9457 pstyle->font_name = parent_font.get()->getTypeFace();
9458 }
9459 UPDATE_STYLE_FIELD( font_family, css_ff_inherit );
9460 //UPDATE_LEN_FIELD( font_size ); // this is done below
9461
9462 // font_features (font-variant/font-feature-settings)
9463 // The specs say a font-variant resets the ones that would be
9464 // inherited (as inheritance always does).
9465 // But, as we store in a single bitmap the values from multiple
9466 // properties (font-variant, font-variant-caps...), we drift from
9467 // the specs by OR'ing the ones sets by style on this node with
9468 // the ones inherited from parents (so we can use style-tweaks
9469 // like body { font-variant: oldstyle-nums; } without that being
9470 // reset by a lower H1 { font-variant: small-caps; }.
9471 // Note that we don't handle the !important bit whether it's set
9472 // for this node or the parent (if it's set on the parent, we
9473 // could decide to = instead of |=), as it's not clear whether
9474 // it's better or not: we'll see.
9475 // (Note that we can use * { font-variant: normal !important; } to
9476 // stop any font-variant without !important from being applied.)
9477 pstyle->font_features.value |= parent_style->font_features.value;
9478 pstyle->font_features.type = css_val_unspecified;
9479
9480 // cr_hint is also a bitmap, and only some bits are inherited.
9481 // A node starts with (css_val_inherited, 0), but if some
9482 // stylesheet has applied some -cr-hint to it, we meet it
9483 // here with (css_val_unspecified, bitmap) and we report the
9484 // inheritable bits from the parent.
9485 // Unless "-cr-hint: none" has been applied to the node, which
9486 // prevents inheritance
9487 if ( !STYLE_HAS_CR_HINT(pstyle, NONE_NO_INHERIT) ) {
9488 pstyle->cr_hint.value |= (parent_style->cr_hint.value & CSS_CR_HINT_INHERITABLE_MASK);
9489 pstyle->cr_hint.type = css_val_unspecified;
9490 }
9491
9492 //UPDATE_LEN_FIELD( text_indent );
9493 spreadParent( pstyle->text_indent, parent_style->text_indent );
9494 switch( pstyle->font_weight )
9495 {
9496 case css_fw_inherit:
9497 pstyle->font_weight = parent_style->font_weight;
9498 break;
9499 case css_fw_normal:
9500 pstyle->font_weight = css_fw_400;
9501 break;
9502 case css_fw_bold:
9503 pstyle->font_weight = css_fw_600;
9504 break;
9505 case css_fw_bolder:
9506 pstyle->font_weight = parent_style->font_weight;
9507 if (pstyle->font_weight < css_fw_800)
9508 {
9509 pstyle->font_weight = (css_font_weight_t)((int)pstyle->font_weight + 2);
9510 }
9511 break;
9512 case css_fw_lighter:
9513 pstyle->font_weight = parent_style->font_weight;
9514 if (pstyle->font_weight > css_fw_200)
9515 {
9516 pstyle->font_weight = (css_font_weight_t)((int)pstyle->font_weight - 2);
9517 }
9518 break;
9519 case css_fw_100:
9520 case css_fw_200:
9521 case css_fw_300:
9522 case css_fw_400:
9523 case css_fw_500:
9524 case css_fw_600:
9525 case css_fw_700:
9526 case css_fw_800:
9527 case css_fw_900:
9528 break;
9529 }
9530 switch( pstyle->font_size.type )
9531 { // ordered here as most likely to be met
9532 case css_val_inherited:
9533 pstyle->font_size = parent_style->font_size;
9534 break;
9535 case css_val_screen_px:
9536 case css_val_px:
9537 // absolute size, nothing to do
9538 break;
9539 case css_val_em: // value = em*256 ; 256 = 1em = x1
9540 pstyle->font_size.type = parent_style->font_size.type;
9541 pstyle->font_size.value = parent_style->font_size.value * pstyle->font_size.value / 256;
9542 break;
9543 case css_val_ex: // value = ex*256 ; 512 = 2ex = 1em = x1
9544 pstyle->font_size.type = parent_style->font_size.type;
9545 pstyle->font_size.value = parent_style->font_size.value * pstyle->font_size.value / 512;
9546 break;
9547 case css_val_percent: // value = percent number ; 100 = 100% => x1
9548 pstyle->font_size.type = parent_style->font_size.type;
9549 pstyle->font_size.value = parent_style->font_size.value * pstyle->font_size.value / 100 / 256;
9550 break;
9551 case css_val_rem:
9552 // not relative to parent, nothing to do
9553 break;
9554 case css_val_in:
9555 case css_val_cm:
9556 case css_val_mm:
9557 case css_val_pt:
9558 case css_val_pc:
9559 // absolute size, nothing to do
9560 break;
9561 case css_val_unspecified:
9562 case css_val_color:
9563 // not supported: use inherited value
9564 pstyle->font_size = parent_style->font_size;
9565 // printf("CRE WARNING: font-size css_val_unspecified or color, fallback to inherited\n");
9566 break;
9567 }
9568
9569 // line_height
9570 if (pstyle->line_height.type == css_val_inherited) {
9571 // We didn't have yet the parent font when dealing with the parent style
9572 // (or we could have computed an absolute size line-height that we could
9573 // have just inherited as-is here).
9574 // But we have it now, so compute its absolute size, so it can be
9575 // inherited as-is by our children.
9576 switch( parent_style->line_height.type ) {
9577 case css_val_percent:
9578 case css_val_em:
9579 case css_val_ex:
9580 {
9581 int pem = parent_font->getSize(); // value in screen px
9582 int line_h = lengthToPx(parent_style->line_height, pem, pem);
9583 // Scale it according to document's _interlineScaleFactor
9584 int interline_scale_factor = doc->getInterlineScaleFactor();
9585 if (interline_scale_factor != INTERLINE_SCALE_FACTOR_NO_SCALE)
9586 line_h = (line_h * interline_scale_factor) >> INTERLINE_SCALE_FACTOR_SHIFT;
9587 pstyle->line_height.value = line_h;
9588 pstyle->line_height.type = css_val_screen_px;
9589 }
9590 break;
9591 // For all others, we can inherit as-is:
9592 // case css_val_rem: // related to font size of the root element
9593 // case css_val_screen_px: // absolute sizes
9594 // case css_val_px:
9595 // case css_val_in:
9596 // case css_val_cm:
9597 // case css_val_mm:
9598 // case css_val_pt:
9599 // case css_val_pc:
9600 // case css_val_unspecified: // unitless number: factor to element's own font size: no relation to parent font
9601 default:
9602 pstyle->line_height = parent_style->line_height; // inherit as-is
9603 break;
9604 }
9605 }
9606
9607 // We can't use spreadParent() with letter_spacing, as there is
9608 // (css_val_unspecified, css_generic_normal) which should not be overriden
9609 // by inheritance, while all other bogus (css_val_unspecified, number) should
9610 if ( pstyle->letter_spacing.type == css_val_inherited ||
9611 (pstyle->letter_spacing.type == css_val_unspecified &&
9612 pstyle->letter_spacing.value != css_generic_normal) )
9613 pstyle->letter_spacing = parent_style->letter_spacing;
9614
9615 spreadParent( pstyle->color, parent_style->color );
9616
9617 // background_color
9618 // Should not be inherited: elements start with unspecified.
9619 // The code will fill the rect of a parent element, and will
9620 // simply draw its children over the already filled rect.
9621 bool spread_background_color = false;
9622 // But for some elements, it should be propagated to children,
9623 // and we explicitely don't have the parent fill the rect with
9624 // its background-color:
9625 // - a TD or TH with unspecified background-color does inherit
9626 // it from its parent TR. We don't draw bgcolor for TR.
9627 if ( pstyle->display == css_d_table_cell )
9628 spread_background_color = true;
9629 // - a TR with unspecified background-color may inherit it from
9630 // its THEAD/TBODY/TFOOT parent (but not from its parent TABLE).
9631 // It may then again be propagated to the TD by the previous rule.
9632 // We don't draw bgcolor for THEAD/TBODY/TFOOT.
9633 if ( pstyle->display == css_d_table_row &&
9634 ( parent_style->display == css_d_table_row_group ||
9635 parent_style->display == css_d_table_header_group ||
9636 parent_style->display == css_d_table_footer_group ) )
9637 spread_background_color = true;
9638 if ( spread_background_color )
9639 spreadParent( pstyle->background_color, parent_style->background_color, true );
9640
9641 // See if applying styles requires pseudo element before/after
9642 bool requires_pseudo_element_before = false;
9643 bool requires_pseudo_element_after = false;
9644 if ( pstyle->pseudo_elem_before_style ) {
9645 if ( pstyle->pseudo_elem_before_style->display != css_d_none
9646 && pstyle->pseudo_elem_before_style->content.length() > 0
9647 && pstyle->pseudo_elem_before_style->content[0] != U'X' ) {
9648 // Not "display: none" and with "content:" different than "none":
9649 // this pseudo element can be generated
9650 requires_pseudo_element_before = true;
9651 }
9652 delete pstyle->pseudo_elem_before_style;
9653 pstyle->pseudo_elem_before_style = NULL;
9654 }
9655 if ( pstyle->pseudo_elem_after_style ) {
9656 if ( pstyle->pseudo_elem_after_style->display != css_d_none
9657 && pstyle->pseudo_elem_after_style->content.length() > 0
9658 && pstyle->pseudo_elem_after_style->content[0] != U'X' ) {
9659 // Not "display: none" and with "content:" different than "none":
9660 // this pseudo element can be generated
9661 requires_pseudo_element_after = true;
9662 }
9663 delete pstyle->pseudo_elem_after_style;
9664 pstyle->pseudo_elem_after_style = NULL;
9665 }
9666
9667 if ( nodeElementId == el_pseudoElem ) {
9668 // Pseudo element ->content may need some update if it contains
9669 // any of the open-quote-like tokens, to account for the
9670 // quoting nested levels. setNodeStyle() is actually the good
9671 // place to do that, as we're visiting all the nodes recursively.
9672 update_style_content_property(pstyle, enode);
9673 }
9674
9675 pstyle->flags = 0; // cleanup, before setStyle() adds it to cache
9676
9677 // set calculated style
9678 //enode->getDocument()->cacheStyle( style );
9679 enode->setStyle( style );
9680 if ( enode->getStyle().isNull() ) {
9681 CRLog::error("NULL style set!!!");
9682 enode->setStyle( style );
9683 }
9684
9685 // set font
9686 enode->initNodeFont();
9687
9688 // Now that this node is fully styled, ensure these pseudo elements
9689 // are there as children, creating them if needed and possible
9690 if ( requires_pseudo_element_before )
9691 enode->ensurePseudoElement(true);
9692 if ( requires_pseudo_element_after )
9693 enode->ensurePseudoElement(false);
9694 }
9695
9696 // Uncomment for debugging getRenderedWidths():
9697 // #define DEBUG_GETRENDEREDWIDTHS
9698
9699 // Estimate width of node when rendered:
9700 // maxWidth: width if it would be rendered on an infinite width area
9701 // minWidth: width with a wrap on all spaces (no hyphenation), so width taken by the longest word
getRenderedWidths(ldomNode * node,int & maxWidth,int & minWidth,int direction,bool ignoreMargin,int rendFlags)9702 void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direction, bool ignoreMargin, int rendFlags) {
9703 // Setup passed-by-reference parameters for recursive calls
9704 int curMaxWidth = 0; // reset on <BR/> or on new block nodes
9705 int curWordWidth = 0; // may not be reset to correctly estimate multi-nodes single-word ("I<sup>er</sup>")
9706 bool collapseNextSpace = true; // collapse leading spaces
9707 int lastSpaceWidth = 0; // trailing spaces width to remove
9708 // These do not need to be passed by reference, as they are only valid for inner nodes/calls
9709 int indent = 0; // text-indent: used on first text, and set again on <BR/>
9710 bool isStartNode = true; // we are starting measurement on that node
9711 // Start measurements and recursions:
9712 getRenderedWidths(node, maxWidth, minWidth, direction, ignoreMargin, rendFlags,
9713 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, NULL, false, isStartNode);
9714 // We took more care with including side bearings into minWidth when considering
9715 // single words, than into maxWidth: so trust minWidth if larger than maxWidth.
9716 if ( maxWidth < minWidth)
9717 maxWidth = minWidth;
9718 }
9719
getRenderedWidths(ldomNode * node,int & maxWidth,int & minWidth,int direction,bool ignoreMargin,int rendFlags,int & curMaxWidth,int & curWordWidth,bool & collapseNextSpace,int & lastSpaceWidth,int indent,TextLangCfg * lang_cfg,bool processNodeAsText,bool isStartNode)9720 void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direction, bool ignoreMargin, int rendFlags,
9721 int &curMaxWidth, int &curWordWidth, bool &collapseNextSpace, int &lastSpaceWidth,
9722 int indent, TextLangCfg * lang_cfg, bool processNodeAsText, bool isStartNode)
9723 {
9724 // This does mostly what renderBlockElement, renderFinalBlock and lvtextfm.cpp
9725 // do, but only with widths and horizontal margin/border/padding and indent
9726 // (with no width constraint, so no line splitting and hyphenation - and we
9727 // don't care about vertical spacing and alignment).
9728 // Limitations: no support for css_d_run_in (hardly ever used, we don't care)
9729 // todo : probably more tweaking to do when direction=RTL, and we should
9730 // also handle direction change when walking inner elements... (For now,
9731 // we only handle list-style-position/text-align combinations vs direction,
9732 // which have different rendering methods.)
9733
9734 #ifdef DEBUG_GETRENDEREDWIDTHS
9735 printf("GRW node: %s\n", UnicodeToLocal(ldomXPointer(node, 0).toString()).c_str());
9736 #endif
9737
9738 if ( node->isElement() && !processNodeAsText ) {
9739 int m = node->getRendMethod();
9740 if (m == erm_invisible)
9741 return;
9742
9743 if ( isStartNode ) {
9744 lang_cfg = TextLangMan::getTextLangCfg( node ); // Fetch it from node or its parents
9745 }
9746 else if ( node->hasAttribute( attr_lang ) ) {
9747 lString32 lang_tag = node->getAttributeValue( attr_lang );
9748 if ( !lang_tag.empty() )
9749 lang_cfg = TextLangMan::getTextLangCfg( lang_tag );
9750 }
9751
9752 if ( isStartNode && node->isBoxingInlineBox() ) {
9753 // The inlineBox is erm_inline, and we'll be measuring it below
9754 // as part of measuring other erm_inline in some erm_final.
9755 // If isStartNode, we want to measure its content, so switch
9756 // to handle it like erm_block.
9757 m = erm_block;
9758 }
9759
9760 css_style_ref_t style = node->getStyle();
9761
9762 // Get image size early
9763 bool is_img = false;
9764 lInt16 img_width = 0;
9765 if ( node->getNodeId()==el_img ) {
9766 is_img = true;
9767 // (as in lvtextfm.cpp LFormattedText::AddSourceObject)
9768 #define DUMMY_IMAGE_SIZE 16
9769 LVImageSourceRef img = node->getObjectImageSource();
9770 if ( img.isNull() )
9771 img = LVCreateDummyImageSource( node, DUMMY_IMAGE_SIZE, DUMMY_IMAGE_SIZE );
9772 lInt16 width = (lUInt16)img->GetWidth();
9773 lInt16 height = (lUInt16)img->GetHeight();
9774 // Scale image native size according to gRenderDPI
9775 width = scaleForRenderDPI(width);
9776 height = scaleForRenderDPI(height);
9777 // Adjust if size defined by CSS
9778 int w = 0, h = 0;
9779 int em = node->getFont()->getSize();
9780 w = lengthToPx(style->width, 100, em);
9781 h = lengthToPx(style->height, 100, em);
9782 if (style->width.type==css_val_percent) w = -w;
9783 if (style->height.type==css_val_percent) h = w*height/width;
9784 if ( w==0 ) {
9785 if ( h==0 ) { // use image native size
9786 w = width;
9787 } else { // use style height, keep aspect ratio
9788 w = width*h/height;
9789 }
9790 }
9791 if (w > 0)
9792 img_width = w;
9793 else { // 0 or styles were in %
9794 // This is a bit tricky...
9795 // When w < 0, the style width was in %, which means % of the
9796 // container width.
9797 // So it does not influence the width we're trying to guess, and will
9798 // adjust to the final width. So, we could let it to be 0.
9799 // But, if this image is the single element of this block, we would
9800 // end up with a minWidth of 0, with no room for the image, and the
9801 // image would be scaled as a % of 0, so to 0.
9802 // So, consider we want the image to be shown as a % of its original
9803 // size: so, our width should be the original image width.
9804 img_width = width;
9805 // With that, it looks like we behave exactly as Firefox, whether
9806 // the image is single in a cell, or surrounded, in this cell
9807 // and/or sibling cells, by small or long text!
9808 // We ensure a minimal size of 1em (so it shows as least the size
9809 // of a letter).
9810 int em = node->getFont()->getSize();
9811 if (img_width < em)
9812 img_width = em;
9813 }
9814 }
9815
9816 if (m == erm_inline) {
9817 if ( is_img ) {
9818 // Get done with previous word
9819 if (curWordWidth > minWidth)
9820 minWidth = curWordWidth;
9821 curWordWidth = 0;
9822 collapseNextSpace = false;
9823 lastSpaceWidth = 0;
9824 if (img_width > 0) { // inline img with a fixed width
9825 maxWidth += img_width;
9826 if (img_width > minWidth)
9827 minWidth = img_width;
9828 }
9829 return;
9830 }
9831 if ( node->getNodeId()==el_br ) {
9832 #ifdef DEBUG_GETRENDEREDWIDTHS
9833 printf("GRW: BR\n");
9834 #endif
9835 // Get done with current word
9836 if (lastSpaceWidth)
9837 curMaxWidth -= lastSpaceWidth;
9838 if (curMaxWidth > maxWidth)
9839 maxWidth = curMaxWidth;
9840 if (curWordWidth > minWidth)
9841 minWidth = curWordWidth;
9842 // First word after a <BR> should not have text-indent in its width,
9843 // but we did reset 'indent' to 0 after the first word of the final block.
9844 // If we get some non-zero indent here, it is "hanging" indent, that
9845 // should be applied to all words, including the one after a <BR/>, and
9846 // so it should contribute to the new line full width (curMaxWidth).
9847 curMaxWidth = indent;
9848 curWordWidth = indent;
9849 collapseNextSpace = true; // skip leading spaces
9850 lastSpaceWidth = 0;
9851 return;
9852 }
9853 if ( node->isBoxingInlineBox() ) {
9854 // Get done with previous word
9855 if (curWordWidth > minWidth)
9856 minWidth = curWordWidth;
9857 curWordWidth = 0;
9858 collapseNextSpace = false;
9859 lastSpaceWidth = 0;
9860 // Get the rendered width of the inlineBox
9861 int _maxw = 0;
9862 int _minw = 0;
9863 getRenderedWidths(node, _maxw, _minw, direction, false, rendFlags);
9864 curMaxWidth += _maxw;
9865 if (_minw > minWidth)
9866 minWidth = _minw;
9867 return;
9868 }
9869 if ( node->getNodeId()==el_pseudoElem ) {
9870 // pseudoElem has no children: reprocess this same node
9871 // with processNodeAsText=true, to process its text content.
9872 getRenderedWidths(node, maxWidth, minWidth, direction, false, rendFlags,
9873 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, lang_cfg, true);
9874 return;
9875 }
9876 // Contains only other inline or text nodes:
9877 // add to our passed by ref *Width
9878 for (int i = 0; i < node->getChildCount(); i++) {
9879 ldomNode * child = node->getChildNode(i);
9880 // Nothing more to do with inline elements: they just carry some
9881 // styles that will be grabbed by children text nodes
9882 getRenderedWidths(child, maxWidth, minWidth, direction, false, rendFlags,
9883 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, lang_cfg);
9884 }
9885 return;
9886 }
9887
9888 // For erm_block and erm_final:
9889 // We may have padding/margin/border, that we can simply add to
9890 // the widths that will be computed by our children.
9891 // Also, if these have % as their CSS unit, we need a width to
9892 // apply the % to, so we can only do that when we get a maxWidth
9893 // and minWidth from children.
9894
9895 // For list-items, we need to compute the bullet width to use either
9896 // as indent, or as left padding
9897 int list_marker_width = 0;
9898 bool list_marker_width_as_padding = false;
9899 if ( style->display == css_d_list_item_block ) {
9900 LFormattedTextRef txform( node->getDocument()->createFormattedText() );
9901 lString32 marker = renderListItemMarker( node, list_marker_width, txform.get(), -1, 0);
9902 #ifdef DEBUG_GETRENDEREDWIDTHS
9903 printf("GRW: list_marker_width: %d\n", list_marker_width);
9904 #endif
9905 bool is_rtl = direction == REND_DIRECTION_RTL;
9906 if ( !renderAsListStylePositionInside(style, is_rtl) ) {
9907 // (same hack as in rendering code: we render 'outside' just
9908 // like 'inside' when center or right aligned)
9909 list_marker_width_as_padding = true;
9910 }
9911 else if ( style->list_style_type == css_lst_none ) {
9912 // When css_lsp_inside, or with that hack when outside & center/right,
9913 // no space should be used if list-style-type: none.
9914 list_marker_width = 0;
9915 }
9916 }
9917
9918 // We use temporary parameters, that we'll add our padding/margin/border to
9919 int _maxWidth = 0;
9920 int _minWidth = 0;
9921
9922 bool use_style_width = false;
9923 css_length_t style_width = style->width;
9924 if ( BLOCK_RENDERING(rendFlags, ENSURE_STYLE_WIDTH) ) {
9925 if ( style->display > css_d_table ) {
9926 // ignore width for table sub-elements
9927 }
9928 else {
9929 // Ignore widths in %, as we can't do much with them
9930 if ( style_width.type != css_val_unspecified && style_width.type != css_val_percent ) {
9931 use_style_width = true;
9932 if ( !BLOCK_RENDERING(rendFlags, ALLOW_STYLE_W_H_ABSOLUTE_UNITS) &&
9933 style_width.type != css_val_percent && style_width.type != css_val_em &&
9934 style_width.type != css_val_ex && style_width.type != css_val_rem ) {
9935 use_style_width = false;
9936 }
9937 if ( node->getNodeId() == el_hr ) {
9938 // We always use style width for <HR> for cosmetic reasons
9939 use_style_width = true;
9940 }
9941 }
9942 }
9943 }
9944
9945 if ( use_style_width ) {
9946 int em = node->getFont()->getSize();
9947 _maxWidth = lengthToPx( style_width, 0, em );
9948 _minWidth = _maxWidth;
9949 }
9950 else if (m == erm_final) {
9951 // Block node that contains only inline or text nodes
9952 if ( is_img ) { // img with display: block always become erm_final (never erm_block)
9953 if (img_width > 0) { // block img with a fixed width
9954 _maxWidth = img_width;
9955 _minWidth = img_width;
9956 }
9957 }
9958 else {
9959 // curMaxWidth and curWordWidth are not used in our parents (which
9960 // are block-like elements), we can just reset them.
9961 curMaxWidth = 0;
9962 curWordWidth = 0;
9963 // We don't have any width yet to use for text-indent in % units,
9964 // but this is very rare - use em as we must use something
9965 int em = node->getFont()->getSize();
9966 indent = lengthToPx(style->text_indent, em, em);
9967 // First word will have text-indent as part of its width
9968 if ( style->text_indent.value & 0x00000001 ) {
9969 // lvstsheet sets the lowest bit to 1 when text-indent has the "hanging" keyword.
9970 // "hanging" means it should apply on all line except the first.
9971 // Hanging indent does not apply on the first word, but may apply on each
9972 // followup word if a wrap happens before them so don't reset it.
9973 // To keep things simple and readable here, we only apply it to the first
9974 // word after a <BR> - but it should really apply on each word, everytime
9975 // we reset curWordWidth, which would make the below code quite ugly and
9976 // hard to understand. Hopefully, negative or hanging indents should be
9977 // rare in floats, inline boxes and table cells.
9978 // (We don't handle the shift/overlap with padding that a real negative
9979 // indent can cause - so, we may return excessive widths.)
9980 }
9981 else {
9982 // Not-"hanging" positive or negative indent applies only on the first line,
9983 // so account for it only on the first word.
9984 curMaxWidth += indent;
9985 curWordWidth += indent;
9986 indent = 0; // but no more on following words in this final node, even after <BR>
9987 }
9988 if (list_marker_width > 0 && !list_marker_width_as_padding) {
9989 // with additional list marker if list-style-position: inside
9990 curMaxWidth += list_marker_width;
9991 curWordWidth += list_marker_width;
9992 }
9993 collapseNextSpace = true; // skip leading spaces
9994 lastSpaceWidth = 0;
9995 // Process children, which are either erm_inline or text nodes
9996 for (int i = 0; i < node->getChildCount(); i++) {
9997 ldomNode * child = node->getChildNode(i);
9998 getRenderedWidths(child, _maxWidth, _minWidth, direction, false, rendFlags,
9999 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, lang_cfg);
10000 // A <BR/> can happen deep among our children, so we deal with that when erm_inline above
10001 }
10002 if ( node->getNodeId() == el_pseudoElem ) {
10003 // erm_final pseudoElem (which has no children): reprocess this same
10004 // node with processNodeAsText=true, to process its text content.
10005 getRenderedWidths(node, _maxWidth, _minWidth, direction, false, rendFlags,
10006 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, lang_cfg, true);
10007 }
10008 if (lastSpaceWidth)
10009 curMaxWidth -= lastSpaceWidth;
10010 // Add current word as we're leaving a block node, so it can't be followed by some other text
10011 if (curMaxWidth > _maxWidth)
10012 _maxWidth = curMaxWidth;
10013 if (curWordWidth > _minWidth)
10014 _minWidth = curWordWidth;
10015 }
10016 }
10017 else if (m == erm_table) {
10018 // Table: a bit hard to estimate a table min/max widths without going
10019 // at rendering it, but let's do our best.
10020 // We can't just add, for each row, the widths of the cells, as:
10021 // | AB | CD | would get sized to contain "EFGHK", while
10022 // | EFGH | K | it should have been sized to contain "EFGHCD"
10023 // | LMNOP |
10024 // So, we need to gather cells min and max widths to compute accurate
10025 // columns min and max widths.
10026 // We won't handle well cells with colspan, as that would be too complicated
10027 // here (we'll still put into columns cells before the first with a colspan).
10028 // So, we'll still compute the addition of each row's cells (which might
10029 // give the right min/max width of a row with colspan): we'll take the
10030 // largest widths from these 2 computations.
10031 typedef struct CellWidths {
10032 int min_w;
10033 int max_w;
10034 int colspan;
10035 int rowspan;
10036 int last_row_idx; // when used as column: index of last row occupied by previous rowspans
10037 CellWidths() : min_w(0), max_w(0), colspan(1), rowspan(1), last_row_idx(-1) {};
10038 CellWidths(int min, int max, int cspan=1, int rspan=1)
10039 : min_w(min), max_w(max), colspan(cspan), rowspan(rspan), last_row_idx(-1) {};
10040 } CellWidths;
10041 typedef LVArray<CellWidths> RowCells;
10042 LVArray<RowCells> table;
10043 int seen_nb_cells = 2; // for RowCells() initial allocation, to avoid realloc
10044 int caption_min_width = 0;
10045 int caption_max_width = 0;
10046
10047 // Non-recursive sub tree walker, to find erm_table_row nodes
10048 ldomNode * n = node;
10049 if ( n && n->getChildCount() > 0 ) {
10050 int index = 0;
10051 n = n->getChildNode(index);
10052 while ( true ) {
10053 // Check the node only the first time we meet it (index == 0) and
10054 // not when we get back to it from a child to process next sibling
10055 if ( index == 0 ) {
10056 if ( n->getRendMethod() == erm_table_row ) {
10057 // Non-recursive sub tree walker found what we are looking for
10058 //
10059 // Measures cells in that row
10060 RowCells row;
10061 row.reserve(seen_nb_cells);
10062 for (int i = 0; i < n->getChildCount(); i++) {
10063 ldomNode * child = n->getChildNode(i);
10064 if ( child->isText() ) {
10065 // Ignore text nodes among table elements (they are usually
10066 // dropped when parsing the HTML, but for <ruby>, parsed as
10067 // inline but later acquiring erm_table* rendering methods,
10068 // we might find some text nodes here.
10069 continue;
10070 }
10071 if ( child->getRendMethod() == erm_invisible ) {
10072 // Ignore invisible nodes (like "<rp>(</rp>" inside <ruby>)
10073 continue;
10074 }
10075 int _maxw = 0;
10076 int _minw = 0;
10077 int _curMaxWidth = 0;
10078 int _curWordWidth = 0;
10079 bool _collapseNextSpace = true;
10080 int _lastSpaceWidth = 0;
10081 getRenderedWidths(child, _maxw, _minw, direction, false, rendFlags,
10082 _curMaxWidth, _curWordWidth, _collapseNextSpace, _lastSpaceWidth, indent, lang_cfg);
10083 int cspan = StrToIntPercent( child->getAttributeValue(attr_colspan).c_str() );
10084 if ( !cspan ) { // 0 if no attribute
10085 // also check obsolete rbspan attribute for <ruby> tables
10086 cspan = StrToIntPercent( child->getAttributeValue(attr_rbspan).c_str() );
10087 if ( !cspan ) {
10088 cspan = 1;
10089 }
10090 }
10091 int rspan = StrToIntPercent( child->getAttributeValue(attr_rowspan).c_str() );
10092 if ( !rspan ) { // 0 if no attribute
10093 rspan = 1;
10094 }
10095 row.add( CellWidths(_minw, _maxw, cspan, rspan) );
10096 }
10097 if ( row.length() > seen_nb_cells )
10098 seen_nb_cells = row.length();
10099 table.add(row);
10100 //
10101 // Non-recursive sub tree walker (continued)
10102 index = n->getChildCount(); // Skip walking/entering that row
10103 }
10104 else if ( n->isElement() && n->getStyle()->display == css_d_table_caption && n->getRendMethod() != erm_invisible ) {
10105 // Also measure caption(s)
10106 int _maxw = 0;
10107 int _minw = 0;
10108 int _curMaxWidth = 0;
10109 int _curWordWidth = 0;
10110 bool _collapseNextSpace = true;
10111 int _lastSpaceWidth = 0;
10112 getRenderedWidths(n, _maxw, _minw, direction, false, rendFlags,
10113 _curMaxWidth, _curWordWidth, _collapseNextSpace, _lastSpaceWidth, indent, lang_cfg);
10114 if ( _minw > caption_min_width )
10115 caption_min_width = _minw;
10116 if ( _maxw > caption_max_width )
10117 caption_max_width = _maxw;
10118 }
10119 }
10120 // Process next child
10121 if ( index < n->getChildCount() ) {
10122 n = n->getChildNode(index);
10123 index = 0;
10124 continue;
10125 }
10126 // No more child, get back to parent and have it process our sibling
10127 index = n->getNodeIndex() + 1;
10128 n = n->getParentNode();
10129 if ( n == node && index >= n->getChildCount() )
10130 break; // back to top node and all its children visited
10131 }
10132 } // Done with non-recursive sub tree walker
10133
10134 // nb_columns is the largest nb of cells+colspan in a row (helps avoiding reallocs)
10135 int nb_columns = 0;
10136 int last_cell_start_column_idx = 0; // to correct nb_columns
10137 for (int r=0; r<table.length(); r++) {
10138 int row_len = 0;
10139 for (int c=0; c<table[r].length(); c++) {
10140 row_len += table[r][c].colspan;
10141 }
10142 if ( row_len > nb_columns ) {
10143 nb_columns = row_len;
10144 }
10145 }
10146 // We still compute cumulative cells widths (might be right when colspan involved)
10147 // Note: this feels like no longer needed now that we handle colspan and rowspan,
10148 // so we won't use them, but let's keep computing them for debugging
10149 int cumulative_min_width = 0;
10150 int cumulative_max_width = 0;
10151 //
10152 RowCells columns(nb_columns, CellWidths()); // Columns widths
10153 // Fill columns accounting for colspan and rowspan, similarly to
10154 // how it's done in the first step of PlaceCells()
10155 for (int r=0; r<table.length(); r++) {
10156 int row_cumul_min_w = 0;
10157 int row_cumul_max_w = 0;
10158 int row_len = table[r].length();
10159 int x = 0; // index of column the current cell will be in
10160 for (int c=0; c<row_len; c++) {
10161 // Find a column that has nothing row-spanning current row
10162 while ( x < nb_columns && r <= columns[x].last_row_idx ) {
10163 x++;
10164 }
10165 if ( last_cell_start_column_idx < x )
10166 last_cell_start_column_idx = x;
10167 // Add columns if necessary, if colspan/rowspan combinations
10168 // exceed what we estimated previously
10169 int cs = table[r][c].colspan;
10170 while ( x + cs-1 > nb_columns-1 ) {
10171 columns.add( CellWidths() );
10172 nb_columns++;
10173 }
10174 // Update columns this cell will colspan with the number
10175 // of rows rowspanned by this cell
10176 int rs = table[r][c].rowspan;
10177 for (int xx=0; xx<cs; xx++) {
10178 if ( columns[x+xx].last_row_idx < r + rs-1 )
10179 columns[x+xx].last_row_idx = r + rs-1;
10180 }
10181 // Update columns this cell will colspan with
10182 // the distributed cell min_w and max_w
10183 int all_min_w = table[r][c].min_w / cs;
10184 int extra_min_w = table[r][c].min_w - all_min_w*cs;
10185 int all_max_w = table[r][c].max_w / cs;
10186 int extra_max_w = table[r][c].max_w - all_max_w*cs;
10187 for (int xx=0; xx<cs; xx++) {
10188 int min_w = all_min_w;
10189 if (extra_min_w > 0) {
10190 min_w++; extra_min_w--;
10191 }
10192 int max_w = all_max_w;
10193 if (extra_max_w > 0) {
10194 max_w++; extra_max_w--;
10195 }
10196 if ( columns[x+xx].min_w < min_w )
10197 columns[x+xx].min_w = min_w;
10198 if ( columns[x+xx].max_w < max_w )
10199 columns[x+xx].max_w = max_w;
10200 }
10201 row_cumul_min_w += table[r][c].min_w;
10202 row_cumul_max_w += table[r][c].max_w;
10203 }
10204 if ( cumulative_min_width < row_cumul_min_w )
10205 cumulative_min_width = row_cumul_min_w;
10206 if ( cumulative_max_width < row_cumul_max_w )
10207 cumulative_max_width = row_cumul_max_w;
10208 }
10209 // Compute sum of columns widths
10210 int columns_min_width = 0;
10211 int columns_max_width = 0;
10212 for (int c=0; c<nb_columns; c++) {
10213 columns_min_width += columns[c].min_w;
10214 columns_max_width += columns[c].max_w;
10215 }
10216 // _minWidth is the max of columns_min_width and caption_min_width (and cumulative_min_width previously)
10217 if ( _minWidth < columns_min_width )
10218 _minWidth = columns_min_width;
10219 if ( _minWidth < caption_min_width )
10220 _minWidth = caption_min_width;
10221 /* This feels like no longer needed, so let's not use them
10222 if ( _minWidth < cumulative_min_width )
10223 _minWidth = cumulative_min_width;
10224 */
10225 // _maxWidth is the max of columns_max_width and caption_max_width (and cumulative_max_width previously)
10226 if ( _maxWidth < columns_max_width )
10227 _maxWidth = columns_max_width;
10228 if ( _maxWidth < caption_max_width )
10229 _maxWidth = caption_max_width;
10230 /* This feels like no longer needed, so let's not use them
10231 if ( _maxWidth < cumulative_max_width )
10232 _maxWidth = cumulative_max_width;
10233 */
10234 // add horizontal border_spacing if "border-collapse: separate"
10235 if ( style->border_collapse != css_border_collapse ) {
10236 int final_nb_cols = nb_columns;
10237 if ( last_cell_start_column_idx < nb_columns-1 )
10238 final_nb_cols = last_cell_start_column_idx + 1;
10239 int em = node->getFont()->getSize();
10240 int extra_width = lengthToPx(style->border_spacing[0], 0, em) * (final_nb_cols+1);
10241 _minWidth += extra_width;
10242 _maxWidth += extra_width;
10243 }
10244 #ifdef DEBUG_GETRENDEREDWIDTHS
10245 printf("GRW table: min %d %d > %d max %d %d > %d\n", columns_min_width, cumulative_min_width,
10246 _minWidth, columns_max_width, cumulative_max_width, _maxWidth);
10247 #endif
10248 }
10249 else { // m == erm_block (or any other we didn't handle specifically)
10250 // Block node that contains other stacked block or final nodes
10251 // Process children, which are all block-like nodes:
10252 // our *Width are the max of our children *Width
10253 for (int i = 0; i < node->getChildCount(); i++) {
10254 // New temporary parameters
10255 int _maxw = 0;
10256 int _minw = 0;
10257 ldomNode * child = node->getChildNode(i);
10258 if ( child->isText() ) {
10259 // Ignore text nodes between block nodes
10260 // (we shouldn't find any, but well)
10261 continue;
10262 }
10263 getRenderedWidths(child, _maxw, _minw, direction, false, rendFlags,
10264 curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, lang_cfg);
10265 if (_maxw > _maxWidth)
10266 _maxWidth = _maxw;
10267 if (_minw > _minWidth)
10268 _minWidth = _minw;
10269 }
10270 }
10271
10272 // For both erm_block or erm_final, adds padding/margin/border
10273 // to _maxWidth and _minWidth (see comment above)
10274
10275 // Style width includes paddings and border in the traditional box model,
10276 // but not in the W3C box model
10277 bool ignorePadding = use_style_width && !BLOCK_RENDERING(rendFlags, USE_W3C_BOX_MODEL);
10278
10279 int padLeft = 0; // these will include padding, border and margin
10280 int padRight = 0;
10281 if (list_marker_width > 0 && list_marker_width_as_padding) {
10282 // with additional left padding for marker if list-style-position: outside
10283 padLeft += list_marker_width;
10284 }
10285 // For % values, we need to reverse-apply them as a whole.
10286 // We can'd do that individually for each, so we aggregate
10287 // the % values.
10288 // (And as we can't ceil() each individually, we'll add 1px
10289 // below for each one to counterbalance rounding errors.)
10290 int padPct = 0; // cumulative percent
10291 int padPctNb = 0; // nb of styles in % (to add 1px)
10292 int em = node->getFont()->getSize();
10293 // margin
10294 if (!ignoreMargin) {
10295 if (style->margin[0].type == css_val_percent) {
10296 padPct += style->margin[0].value;
10297 padPctNb += 1;
10298 }
10299 else {
10300 int margin = lengthToPx( style->margin[0], 0, em );
10301 if ( margin > 0 || BLOCK_RENDERING(rendFlags, ALLOW_HORIZONTAL_NEGATIVE_MARGINS) ) {
10302 padLeft += margin;
10303 }
10304 }
10305 if (style->margin[1].type == css_val_percent) {
10306 padPct += style->margin[1].value;
10307 padPctNb += 1;
10308 }
10309 else {
10310 int margin = lengthToPx( style->margin[1], 0, em );
10311 if ( margin > 0 || BLOCK_RENDERING(rendFlags, ALLOW_HORIZONTAL_NEGATIVE_MARGINS) ) {
10312 padRight += margin;
10313 }
10314 }
10315 }
10316 if (!ignorePadding) {
10317 // padding
10318 if (style->padding[0].type == css_val_percent) {
10319 padPct += style->padding[0].value;
10320 padPctNb += 1;
10321 }
10322 else
10323 padLeft += lengthToPx( style->padding[0], 0, em );
10324 if (style->padding[1].type == css_val_percent) {
10325 padPct += style->padding[1].value;
10326 padPctNb += 1;
10327 }
10328 else
10329 padRight += lengthToPx( style->padding[1], 0, em );
10330 // border (which does not accept units in %)
10331 padLeft += measureBorder(node,3);
10332 padRight += measureBorder(node,1);
10333 }
10334 // Add the non-pct values to make our base to invert-apply padPct
10335 _minWidth += padLeft + padRight;
10336 _maxWidth += padLeft + padRight;
10337 // For length in %, the % (P, padPct) should be from the outer width (L),
10338 // but we have only the inner width (w). We have w and P, we want L-w (m).
10339 // m = L*P and w = L - m
10340 // w = L - L*P = L*(1-P)
10341 // L = w/(1-P)
10342 // m = L - w = w/(1-P) - w = w*(1 - (1-P))/(1-P) = w*P/(1-P)
10343 // css_val_percent value are *256 (100% = 100*256)
10344 // We ignore a total of 100% (no space for content, and division by zero here
10345 int minPadPctLen = 0;
10346 int maxPadPctLen = 0;
10347 if (padPctNb > 0 && padPct != 100*256) {
10348 // add padPctNb: 1px for each value in %
10349 minPadPctLen = _minWidth * padPct / (100*256-padPct) + padPctNb;
10350 maxPadPctLen = _maxWidth * padPct / (100*256-padPct) + padPctNb;
10351 _minWidth += minPadPctLen;
10352 _maxWidth += maxPadPctLen;
10353 }
10354 #ifdef DEBUG_GETRENDEREDWIDTHS
10355 printf("GRW blk: pad min+ %d %d +%d%%=%d\n", padLeft, padRight, padPct, minPadPctLen);
10356 printf("GRW blk: pad max+ %d %d +%d%%=%d\n", padLeft, padRight, padPct, maxPadPctLen);
10357 #endif
10358 // We must have been provided with maxWidth=0 and minWidth=0 (temporary
10359 // parameters set by outer calls), but do these regular checks anyway.
10360 if (_maxWidth > maxWidth)
10361 maxWidth = _maxWidth;
10362 if (_minWidth > minWidth)
10363 minWidth = _minWidth;
10364 }
10365 else { // text or pseudoElem
10366 lString32 text;
10367 int start = 0;
10368 int len = 0;
10369 ldomNode * parent;
10370 if ( node->isText() ) {
10371 text = node->getText();
10372 parent = node->getParentNode();
10373 }
10374 else if ( node->getNodeId() == el_pseudoElem ) {
10375 text = get_applied_content_property(node);
10376 parent = node; // this pseudoElem node carries the font and style of the text
10377 if ( isStartNode ) {
10378 lang_cfg = TextLangMan::getTextLangCfg( node ); // Fetch it from node or its parents
10379 }
10380 }
10381 else {
10382 return;
10383 }
10384 len = text.length();
10385 if ( len == 0 )
10386 return;
10387 // letter-spacing
10388 LVFontRef font = parent->getFont();
10389 int em = font->getSize();
10390 css_style_ref_t parent_style = parent->getStyle();
10391 int letter_spacing = lengthToPx(parent_style->letter_spacing, em, em);
10392 // text-transform
10393 switch (parent_style->text_transform) {
10394 case css_tt_uppercase:
10395 text.uppercase();
10396 break;
10397 case css_tt_lowercase:
10398 text.lowercase();
10399 break;
10400 case css_tt_capitalize:
10401 text.capitalize();
10402 break;
10403 case css_tt_full_width:
10404 // text.fullWidthChars(); // disabled for now (may change CJK rendering)
10405 break;
10406 case css_tt_none:
10407 case css_tt_inherit:
10408 break;
10409 }
10410 // white-space
10411 // When getting min width, ensure non free wrap for "white-space: pre" (even if we
10412 // don't when rendering). Others like "pre-wrap" and "pre-line" are allowed to wrap.
10413 bool nowrap = (parent_style->white_space == css_ws_nowrap) || (parent_style->white_space == css_ws_pre);
10414 bool pre = parent_style->white_space >= css_ws_pre;
10415 int space_width_scale_percent = pre ? 100 : parent->getDocument()->getSpaceWidthScalePercent();
10416
10417 // If fit_glyphs, we'll adjust below each word width with calls to
10418 // getLeftSideBearing() and getRightSideBearing(). These should be
10419 // called with the exact same parameters as used in lvtextfm.cpp
10420 // addLine(). (Previously, we adjusted overflows and underflows on
10421 // the left, and only overflows on the right. We now only adjust
10422 // overflows on both sides - but don't touch underflows to keep
10423 // the text natural alignment.)
10424 // bool fit_glyphs = STYLE_HAS_CR_HINT(parent_style, FIT_GLYPHS);
10425 //
10426 // Best to always measure accounting for overflows: we don't know
10427 // what adjusments lvtextfm.cpp AddLine() will do depending on
10428 // the usable_left/right_overflows it got.
10429 // (Let's keep this easily toggable in case we need it.)
10430 #define fit_glyphs true
10431
10432 // measure text
10433 const lChar32 * txt = text.c_str();
10434 #ifdef DEBUG_GETRENDEREDWIDTHS
10435 printf("GRW text: |%s|\n", UnicodeToLocal(text).c_str());
10436 printf("GRW text: (dumb text size=%d)\n", font->getTextWidth(txt, len));
10437 #endif
10438 #define MAX_TEXT_CHUNK_SIZE 4096
10439 static lUInt16 widths[MAX_TEXT_CHUNK_SIZE+1];
10440 static lUInt8 flags[MAX_TEXT_CHUNK_SIZE+1];
10441
10442 // todo: use fribidi and split measurement at fribidi level change,
10443 // and beware left/right side bearing adjustments...
10444 #if (USE_LIBUNIBREAK==1)
10445 // If using libunibreak, we do similarly as in lvtextfm.cpp copyText(),
10446 // except that we don't update previous char, but look ahead at next
10447 // char to know about current break.
10448 // Also, as we do all that only text node by text node, we may lose
10449 // line breaking rules between contiguous text nodes (but it's a bit
10450 // complicated to pass this lbCtx across calls...)
10451 struct LineBreakContext lbCtx;
10452 lb_init_break_context(&lbCtx, 0x200D, NULL); // ZERO WIDTH JOINER
10453 lbCtx.lbpLang = lang_cfg->getLBProps();
10454 lb_process_next_char(&lbCtx, (utf32_t)(*txt));
10455 #endif
10456 while (true) {
10457 int chars_measured = font->measureText(
10458 txt + start,
10459 len,
10460 widths, flags,
10461 0x7FFF, // very wide width
10462 '?', // replacement char
10463 lang_cfg,
10464 letter_spacing,
10465 false); // no hyphenation
10466 // todo: provide direction and hints
10467 #if (USE_LIBUNIBREAK==1)
10468 for (int i=0; i<chars_measured; i++) {
10469 if (pre) {
10470 collapseNextSpace = false; // Reset it if set previously
10471 }
10472 int w = widths[i] - (i>0 ? widths[i-1] : 0);
10473 if ( (flags[i] & LCHAR_IS_SPACE) && (space_width_scale_percent != 100) ) {
10474 w = w * space_width_scale_percent / 100;
10475 }
10476 lChar32 c = *(txt + start + i);
10477 lChar32 next_c = *(txt + start + i + 1); // might be 0 at end of string
10478 if ( lang_cfg->hasLBCharSubFunc() ) {
10479 next_c = lang_cfg->getLBCharSubFunc()(&lbCtx, txt+start, i+1, len-1 - (i+1));
10480 }
10481 int brk = lb_process_next_char(&lbCtx, (utf32_t)next_c);
10482 // We don't really need to bother with consecutive spaces (that
10483 // should collapse when not 'pre', but libunibreak only allows
10484 // break on the last one, so we would get the leading spaces
10485 // width as part of current word), as we're dealing with a single
10486 // text node, and the HTML parser has removed multiple consecutive
10487 // spaces (except with 'pre', where it looks fine as they don't
10488 // collapse; this might still not be right with pre-wrap though).
10489 // printf("between <%c%c>: brk %d\n", c, next_c, brk);
10490 if (brk == LINEBREAK_ALLOWBREAK && !nowrap) {
10491 if (flags[i] & LCHAR_ALLOW_WRAP_AFTER) { // a breakable/collapsible space (flag set by measureText()
10492 if (collapseNextSpace) // ignore this space
10493 continue;
10494 collapseNextSpace = true; // ignore next spaces, even if in another node
10495 lastSpaceWidth = pre ? 0 : w; // Don't remove last space width if 'pre'
10496 curMaxWidth += w; // add this space to non-wrap width
10497 if (fit_glyphs && curWordWidth > 0) { // there was a word before this space
10498 if (start+i > 0) {
10499 // adjust for last word's last char overflow (italic, letter f...)
10500 lChar32 prevc = *(txt + start + i - 1);
10501 int right_overflow = - font->getRightSideBearing(prevc, true);
10502 curWordWidth += right_overflow;
10503 }
10504 }
10505 if (curWordWidth > minWidth) // done with previous word
10506 minWidth = curWordWidth; // longest word found
10507 curWordWidth = 0;
10508 }
10509 else { // break after a non space: might be a CJK char (or other stuff)
10510 collapseNextSpace = false; // next space should not be ignored
10511 lastSpaceWidth = 0; // no width to take off if we stop with this char
10512 curMaxWidth += w;
10513 if (fit_glyphs && curWordWidth > 0) { // there was a word or CJK char before this CJK char
10514 if (start+i > 0) {
10515 // adjust for last word's last char or previous CJK char right overflow
10516 lChar32 prevc = *(txt + start + i - 1);
10517 int right_overflow = - font->getRightSideBearing(prevc, true);
10518 curWordWidth += right_overflow;
10519 }
10520 }
10521 if (curWordWidth > minWidth) // done with previous word
10522 minWidth = curWordWidth; // longest word found
10523 curWordWidth = w;
10524 if (fit_glyphs) {
10525 // adjust for leading overflow
10526 int left_overflow = - font->getLeftSideBearing(c, true);
10527 curWordWidth += left_overflow;
10528 if (start + i == 0) // at start of text only? (not sure)
10529 curMaxWidth += left_overflow; // also add it to max width
10530 }
10531 }
10532 }
10533 else if (brk == LINEBREAK_MUSTBREAK) { // \n if pre
10534 // Get done with current word
10535 if (fit_glyphs && curWordWidth > 0) { // we end with a word
10536 if (start+i > 0) {
10537 // adjust for last word's last char or previous CJK char right overflow
10538 lChar32 prevc = *(txt + start + i - 1);
10539 int right_overflow = - font->getRightSideBearing(prevc, true);
10540 curWordWidth += right_overflow;
10541 curMaxWidth += right_overflow;
10542 }
10543 }
10544 // Similar to what's done above on <BR> or at end of final node
10545 if (lastSpaceWidth)
10546 curMaxWidth -= lastSpaceWidth;
10547 if (curMaxWidth > maxWidth)
10548 maxWidth = curMaxWidth;
10549 if (curWordWidth > minWidth)
10550 minWidth = curWordWidth;
10551 // Get ready for next text
10552 curMaxWidth = indent;
10553 curWordWidth = indent;
10554 collapseNextSpace = true; // skip leading spaces
10555 lastSpaceWidth = 0;
10556 }
10557 else { // break not allowed: this char is part of a word
10558 // But it can be a space followed by another space (with libunibreak,
10559 // only the last space will get LINEBREAK_ALLOWBREAK).
10560 if (flags[i] & LCHAR_ALLOW_WRAP_AFTER) { // a breakable/collapsible space (flag set by measureText()
10561 if (collapseNextSpace) { // space before (and space after)
10562 continue; // ignore it
10563 }
10564 collapseNextSpace = true; // ignore next ones
10565 lastSpaceWidth = pre ? 0 : w; // Don't remove last space width if 'pre'
10566 }
10567 else { // Not a space
10568 collapseNextSpace = false; // next space should not be ignored
10569 lastSpaceWidth = 0; // no width to take off if we stop with this char
10570 }
10571 if (fit_glyphs && curWordWidth == 0) { // first char of a word
10572 // adjust for leading overflow on first char of a word
10573 int left_overflow = - font->getLeftSideBearing(c, true);
10574 curWordWidth += left_overflow;
10575 if (start + i == 0) // at start of text only? (not sure)
10576 curMaxWidth += left_overflow; // also add it to max width
10577 }
10578 curMaxWidth += w;
10579 curWordWidth += w;
10580 // libunibreak should handle properly '/' in urls (except may be
10581 // if the url parts are made of numbers...)
10582 }
10583 }
10584 #else // not USE_LIBUNIBREAK==1
10585 // (This has not been updated to handle nowrap & pre)
10586 (void)nowrap; // avoid clang warning: value stored is never read
10587 for (int i=0; i<chars_measured; i++) {
10588 int w = widths[i] - (i>0 ? widths[i-1] : 0);
10589 lChar32 c = *(txt + start + i);
10590 if ( (flags[i] & LCHAR_IS_SPACE) && (space_width_scale_percent != 100) ) {
10591 w = w * space_width_scale_percent / 100;
10592 }
10593 bool is_cjk = (c >= UNICODE_CJK_IDEOGRAPHS_BEGIN && c <= UNICODE_CJK_IDEOGRAPHS_END
10594 && ( c<=UNICODE_CJK_PUNCTUATION_HALF_AND_FULL_WIDTH_BEGIN
10595 || c>=UNICODE_CJK_PUNCTUATION_HALF_AND_FULL_WIDTH_END) );
10596 // Do we need to do something about CJK punctuation?
10597 // Having CJK columns min_width the width of a single CJK char
10598 // may, on some pages, make some table cells have a single
10599 // CJK char per line, which can look uglier than when not
10600 // dealing with them specifically (see with: bool is_cjk=false).
10601 // But Firefox does that too, may be a bit less radically than
10602 // us, so our table algorithm may need some tweaking...
10603 if (flags[i] & LCHAR_ALLOW_WRAP_AFTER) { // A space
10604 if (collapseNextSpace) // ignore this space
10605 continue;
10606 collapseNextSpace = true; // ignore next spaces, even if in another node
10607 lastSpaceWidth = w;
10608 curMaxWidth += w; // add this space to non-wrap width
10609 if (fit_glyphs && curWordWidth > 0) { // there was a word before this space
10610 if (start+i > 0) {
10611 // adjust for last word's last char overflow (italic, letter f...)
10612 lChar32 prevc = *(txt + start + i - 1);
10613 int right_overflow = - font->getRightSideBearing(prevc, true);
10614 curWordWidth += right_overflow;
10615 }
10616 }
10617 if (curWordWidth > minWidth) // done with previous word
10618 minWidth = curWordWidth; // longest word found
10619 curWordWidth = 0;
10620 }
10621 else if (is_cjk) { // CJK chars are themselves a word
10622 collapseNextSpace = false; // next space should not be ignored
10623 lastSpaceWidth = 0; // no width to take off if we stop with this char
10624 curMaxWidth += w;
10625 if (fit_glyphs && curWordWidth > 0) { // there was a word or CJK char before this CJK char
10626 if (start+i > 0) {
10627 // adjust for last word's last char or previous CJK char right overflow
10628 lChar32 prevc = *(txt + start + i - 1);
10629 int right_overflow = - font->getRightSideBearing(prevc, true);
10630 curWordWidth += right_overflow;
10631 }
10632 }
10633 if (curWordWidth > minWidth) // done with previous word
10634 minWidth = curWordWidth; // longest word found
10635 curWordWidth = w;
10636 if (fit_glyphs) {
10637 // adjust for leading overflow
10638 int left_overflow = - font->getLeftSideBearing(c, true);
10639 curWordWidth += left_overflow;
10640 if (start + i == 0) // at start of text only? (not sure)
10641 curMaxWidth += left_overflow; // also add it to max width
10642 }
10643 }
10644 else { // A char part of a word
10645 collapseNextSpace = false; // next space should not be ignored
10646 lastSpaceWidth = 0; // no width to take off if we stop with this char
10647 if (fit_glyphs && curWordWidth == 0) { // first char of a word
10648 // adjust for leading overflow on first char of a word
10649 int left_overflow = - font->getLeftSideBearing(c, true);
10650 curWordWidth += left_overflow;
10651 if (start + i == 0) // at start of text only? (not sure)
10652 curMaxWidth += left_overflow; // also add it to max width
10653 }
10654 curMaxWidth += w;
10655 curWordWidth += w;
10656 // Try to guess long urls or hostnames, and split a word on
10657 // each / or dot not followed by a space, so they are not
10658 // a super long word and don't over extend minWidth.
10659 if ( (c == '/' || c == '.') &&
10660 i < start+len-1 && *(txt + start + i + 1) != ' ') {
10661 if (curWordWidth > minWidth)
10662 minWidth = curWordWidth;
10663 curWordWidth = 0;
10664 }
10665 }
10666 }
10667 #endif // not USE_LIBUNIBREAK==1
10668 if ( chars_measured == len ) { // done with this text node
10669 if (fit_glyphs && curWordWidth > 0) { // we end with a word
10670 if (start+len > 0) {
10671 // adjust for word last char right overflow
10672 lChar32 prevc = *(txt + start + len - 1);
10673 int right_overflow = - font->getRightSideBearing(prevc, true);
10674 curWordWidth += right_overflow;
10675 curMaxWidth += right_overflow; // also add it to max width
10676 }
10677 }
10678 break;
10679 }
10680 // continue measuring
10681 len -= chars_measured;
10682 start += chars_measured;
10683 }
10684 }
10685 #ifdef DEBUG_GETRENDEREDWIDTHS
10686 printf("GRW current: max=%d word=%d (max=%d, min=%d)\n", curMaxWidth, curWordWidth, maxWidth, minWidth);
10687 #endif
10688 }
10689