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