diff --git a/crengine/include/cssdef.h b/crengine/include/cssdef.h
index 8507e64b0..cb8b92e96 100644
--- a/crengine/include/cssdef.h
+++ b/crengine/include/cssdef.h
@@ -347,6 +347,9 @@ enum css_generic_value_t {
// (it could have been a non-standard named value for line-height:, but we want to be
// able to not override existing line-height: values)
#define CSS_CR_HINT_STRUT_CONFINED 0x00000002 // -cr-hint: strut-confined (inheritable)
+// Glyphs should not overflow line edges, and across text nodes or over images
+// (it can also be used to disable hanging punctuation on the targeted nodes).
+#define CSS_CR_HINT_FIT_GLYPHS 0x00000004 // -cr-hint: fit-glyphs (inheritable)
// A node with these should be considered as TOC item of level N when building alternate TOC
#define CSS_CR_HINT_TOC_LEVEL1 0x00000100 // -cr-hint: toc-level1
@@ -380,7 +383,7 @@ enum css_generic_value_t {
// everything else indicates it is)
// A few of them are inheritable, most are not.
-#define CSS_CR_HINT_INHERITABLE_MASK 0x00000002
+#define CSS_CR_HINT_INHERITABLE_MASK 0x00000006
// Macro for easier checking
#define STYLE_HAS_CR_HINT(s, h) ( (bool)(s->cr_hint.value & CSS_CR_HINT_##h) )
diff --git a/crengine/include/lvrend.h b/crengine/include/lvrend.h
index 5926dc9db..0e72de34e 100644
--- a/crengine/include/lvrend.h
+++ b/crengine/include/lvrend.h
@@ -92,7 +92,7 @@ class BlockFloatFootprint {
// Floats to transfer to final block for it to
// start with these 5 "fake" embedded floats
int floats_cnt;
- int floats[5][5]; // max 5 floats, with (x,y,w,h,is_right) each
+ int floats[5][6]; // max 5 floats, with (x,y,w,h,is_right,inward_margin) each
int getFinalMinY() { return used_min_y; };
int getFinalMaxY() { return used_max_y; };
@@ -127,9 +127,11 @@ lUInt32 styleToTextFmtFlags( bool is_block, const css_style_ref_t & style, lUInt
void renderFinalBlock( ldomNode * node, LFormattedText * txform, RenderRectAccessor * fmt, lUInt32 & flags,
int indent, int line_h, TextLangCfg * lang_cfg=NULL, int valign_dy=0, bool * is_link_start=NULL );
/// renders block which contains subblocks (with gRenderBlockRenderingFlags as flags)
-int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction=REND_DIRECTION_UNSET, int * baseline=NULL );
+int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
+ int usable_left_overflow=0, int usable_right_overflow=0, int direction=REND_DIRECTION_UNSET, int * baseline=NULL );
/// renders block which contains subblocks
-int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction, int * baseline, int rend_flags );
+int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
+ int usable_left_overflow, int usable_right_overflow, int direction, int * baseline, int rend_flags );
/// renders table element
int renderTable( LVRendPageContext & context, ldomNode * element, int x, int y, int width,
bool shrink_to_fit, int & fitted_width, int direction=REND_DIRECTION_UNSET,
@@ -173,6 +175,8 @@ extern int gRenderDPI;
extern bool gRenderScaleFontWithDPI;
extern int gRootFontSize;
+extern bool gHangingPunctuationEnabled;
+
#define INTERLINE_SCALE_FACTOR_NO_SCALE 1024
#define INTERLINE_SCALE_FACTOR_SHIFT 10
extern int gInterlineScaleFactor;
diff --git a/crengine/include/lvstyles.h b/crengine/include/lvstyles.h
index 16f71ff64..d36e3c2d3 100644
--- a/crengine/include/lvstyles.h
+++ b/crengine/include/lvstyles.h
@@ -321,6 +321,9 @@ class lvdomElementFormatRec {
// is also limited to that for being carried in lInt16 slots when
// formatting text in lvtextfm.cpp.)
+ short _usable_left_overflow; // Usable overflow for hanging punctuation
+ short _usable_right_overflow; // and glyphs negative side bearings
+
// Children blocks should be fully contained in their parent block,
// and sibling nodes blocks should not overlap with other siblings,
// except when float are involved and we allow them to continue
@@ -351,18 +354,19 @@ class lvdomElementFormatRec {
int _listprop_node_idx; // dataIndex of the UL/OL node this erm_final block
// should get its marker from
- // Added for padding from 15 to 16 32-bits ints
- int _available1;
+ // We're now at 16 32-bits ints. If needing new fields, add some padding:
+ // Added for padding from 17 to 20 32-bits ints
+ // int _available1; int _available2; int _available3;
public:
lvdomElementFormatRec()
: _x(0), _width(0), _y(0), _height(0)
, _inner_width(0), _inner_x(0), _inner_y(0), _baseline(0)
+ , _usable_left_overflow(0), _usable_right_overflow(0)
, _top_overflow(0), _bottom_overflow(0)
, _lang_node_idx(0) , _listprop_node_idx(0)
, _flags(0), _extra0(0)
, _extra1(0), _extra2(0), _extra3(0), _extra4(0), _extra5(0)
- , _available1(0)
{
}
~lvdomElementFormatRec()
@@ -372,23 +376,23 @@ class lvdomElementFormatRec {
{
_x = _width = _y = _height = 0;
_inner_width = _inner_x = _inner_y = _baseline = 0;
+ _usable_left_overflow = _usable_right_overflow = 0;
_top_overflow = _bottom_overflow = 0;
_lang_node_idx = _listprop_node_idx = 0;
_flags = _extra0 = 0;
_extra1 = _extra2 = _extra3 = _extra4 = _extra5 = 0;
- _available1 = 0;
}
bool operator == ( lvdomElementFormatRec & v )
{
return (_height==v._height && _y==v._y && _width==v._width && _x==v._x &&
_inner_width==v._inner_width && _inner_x==v._inner_x &&
_inner_y==v._inner_y && _baseline==v._baseline &&
+ _usable_left_overflow==v._usable_left_overflow && _usable_right_overflow==v._usable_right_overflow &&
_top_overflow==v._top_overflow && _bottom_overflow==v._bottom_overflow &&
_lang_node_idx==v._lang_node_idx && _listprop_node_idx==v._listprop_node_idx &&
_flags==v._flags && _extra0==v._extra0 &&
_extra1==v._extra1 && _extra2==v._extra2 && _extra3==v._extra3 &&
- _extra4==v._extra4 && _extra5==v._extra5 &&
- _available1==v._available1
+ _extra4==v._extra4 && _extra5==v._extra5
);
}
bool operator != ( lvdomElementFormatRec & v )
@@ -396,12 +400,12 @@ class lvdomElementFormatRec {
return (_height!=v._height || _y!=v._y || _width!=v._width || _x!=v._x ||
_inner_width!=v._inner_width || _inner_x!=v._inner_x ||
_inner_y!=v._inner_y || _baseline!=v._baseline ||
+ _usable_left_overflow!=v._usable_left_overflow || _usable_right_overflow!=v._usable_right_overflow ||
_top_overflow!=v._top_overflow || _bottom_overflow!=v._bottom_overflow ||
_lang_node_idx!=v._lang_node_idx || _listprop_node_idx!=v._listprop_node_idx ||
_flags!=v._flags || _extra0!=v._extra0 ||
_extra1!=v._extra1 || _extra2!=v._extra2 || _extra3!=v._extra3 ||
- _extra4!=v._extra4 || _extra5!=v._extra5 ||
- _available1!=v._available1
+ _extra4!=v._extra4 || _extra5!=v._extra5
);
}
// Get/Set
diff --git a/crengine/include/lvtextfm.h b/crengine/include/lvtextfm.h
index 5a3845462..b6d71e868 100644
--- a/crengine/include/lvtextfm.h
+++ b/crengine/include/lvtextfm.h
@@ -19,9 +19,6 @@
#include "lvbmpbuf.h"
#include "textlang.h"
-// comment out following line to use old formatter
-#define USE_NEW_FORMATTER 1
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -81,7 +78,8 @@ extern "C" {
#define LTEXT_SRC_IS_CLEAR_BOTH 0x03000000 // text follows
#define LTEXT_SRC_IS_CLEAR_LAST 0x04000000 // ignorable text, added when nothing follows
-#define LTEXT__AVAILABLE_BIT_28__ 0x08000000
+#define LTEXT_FIT_GLYPHS 0x08000000 // Avoid glyph overflows and override at line edges and between text nodes
+
#define LTEXT__AVAILABLE_BIT_29__ 0x10000000
#define LTEXT__AVAILABLE_BIT_30__ 0x20000000
#define LTEXT__AVAILABLE_BIT_31__ 0x40000000
@@ -212,7 +210,8 @@ typedef struct
lInt32 y; /**< start y position of float */
lInt16 x; /**< start x position */
lUInt16 width; /**< width */
- lUInt16 height; /**< height */
+ lUInt32 height; /**< height */
+ lUInt16 inward_margin; /**< inward margin */
css_clear_t clear; /**< clear: css property value */
bool is_right; /**< is float: right */
bool to_position; /**< not yet positionned */
@@ -432,6 +431,8 @@ class LFormattedText
lUInt32 Format(lUInt16 width, lUInt16 page_height,
int para_direction=0, // = REND_DIRECTION_UNSET in lvrend.h
+ int usable_left_overflow=0, int usable_right_overflow=0,
+ bool hanging_punctuation=false,
BlockFloatFootprint * float_footprint = NULL );
int GetSrcCount()
@@ -481,6 +482,4 @@ class LFormattedText
#endif
-extern bool gFlgFloatingPunctuationEnabled;
-
#endif
diff --git a/crengine/include/lvtinydom.h b/crengine/include/lvtinydom.h
index c045c305f..e50381791 100644
--- a/crengine/include/lvtinydom.h
+++ b/crengine/include/lvtinydom.h
@@ -723,6 +723,11 @@ class RenderRectAccessor : public lvdomElementFormatRec
void setInnerY( int y );
void setInnerWidth( int w );
+ int getUsableLeftOverflow();
+ int getUsableRightOverflow();
+ void setUsableLeftOverflow( int dx );
+ void setUsableRightOverflow( int dx );
+
int getTopOverflow();
int getBottomOverflow();
void setTopOverflow( int dy );
@@ -2514,9 +2519,12 @@ class ldomDocument : public lxmlDocBase
#if BUILD_LITE!=1
bool isRendered() { return _rendered; }
/// renders (formats) document in memory: returns true if re-rendering needed, false if not
- virtual bool render( LVRendPageList * pages, LVDocViewCallback * callback, int width, int dy, bool showCover, int y0, font_ref_t def_font, int def_interline_space, CRPropRef props );
+ virtual bool render( LVRendPageList * pages, LVDocViewCallback * callback, int width, int dy,
+ bool showCover, int y0, font_ref_t def_font, int def_interline_space,
+ CRPropRef props, int usable_left_overflow=0, int usable_right_overflow=0 );
/// set global rendering properties
- virtual bool setRenderProps( int width, int dy, bool showCover, int y0, font_ref_t def_font, int def_interline_space, CRPropRef props );
+ virtual bool setRenderProps( int width, int dy, bool showCover, int y0, font_ref_t def_font,
+ int def_interline_space, CRPropRef props );
#endif
/// create xpointer from pointer string
ldomXPointer createXPointer( const lString16 & xPointerStr );
diff --git a/crengine/include/textlang.h b/crengine/include/textlang.h
index d22f69af9..69686ed00 100644
--- a/crengine/include/textlang.h
+++ b/crengine/include/textlang.h
@@ -142,6 +142,9 @@ class TextLangCfg
lString16 & getOpeningQuote( bool update_level=true );
lString16 & getClosingQuote( bool update_level=true );
+ int getHyphenHangingPercent();
+ int getHangingPercent( bool right_hanging, bool & check_font, const lChar16 * text, int pos, int next_usable );
+
#if USE_HARFBUZZ==1
hb_language_t getHBLanguage() const { return _hb_language; }
#endif
diff --git a/crengine/src/lvdocview.cpp b/crengine/src/lvdocview.cpp
index 802790111..a130171a7 100644
--- a/crengine/src/lvdocview.cpp
+++ b/crengine/src/lvdocview.cpp
@@ -2779,7 +2779,8 @@ void LVDocView::Render(int dx, int dy, LVRendPageList * pages) {
//CRLog::trace("calling render() for document %08X font=%08X", (unsigned int)m_doc, (unsigned int)m_font.get() );
bool did_rerender = m_doc->render(pages, isDocumentOpened() ? m_callback : NULL, dx, dy,
m_showCover, m_showCover ? dy + m_pageMargins.bottom * 4 : 0,
- m_font, m_def_interline_space, m_props);
+ m_font, m_def_interline_space, m_props,
+ m_pageMargins.left, m_pageMargins.right);
#if 0
// For debugging lvpagesplitter.cpp (small books)
@@ -6244,7 +6245,9 @@ CRPropRef LVDocView::propsApply(CRPropRef props) {
if ((int)fontMan->GetHintingMode() != mode && mode>=0 && mode<=2) {
//CRLog::debug("Setting hinting mode to %d", mode);
fontMan->SetHintingMode((hinting_mode_t)mode);
- requestRender(); // does m_doc->clearRendBlockCache(), which is needed on hinting mode change
+ REQUEST_RENDER("propsApply - font hinting")
+ // requestRender() does m_doc->clearRendBlockCache(), which is needed
+ // on hinting mode change
}
} else if (name == PROP_HIGHLIGHT_SELECTION_COLOR || name == PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT || name == PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT) {
REQUEST_RENDER("propsApply - highlight")
@@ -6260,7 +6263,7 @@ CRPropRef LVDocView::propsApply(CRPropRef props) {
if ((int)fontMan->GetKerningMode() != mode && mode>=0 && mode<=3) {
//CRLog::debug("Setting kerning mode to %d", mode);
fontMan->SetKerningMode((kerning_mode_t)mode);
- requestRender();
+ REQUEST_RENDER("propsApply - font kerning")
}
} else if (name == PROP_FONT_WEIGHT_EMBOLDEN) {
bool embolden = props->getBoolDef(PROP_FONT_WEIGHT_EMBOLDEN, false);
@@ -6463,9 +6466,11 @@ CRPropRef LVDocView::propsApply(CRPropRef props) {
REQUEST_RENDER("propsApply footnotes")
} else if (name == PROP_FLOATING_PUNCTUATION) {
bool value = props->getBoolDef(PROP_FLOATING_PUNCTUATION, true);
- if ( gFlgFloatingPunctuationEnabled != value ) {
- gFlgFloatingPunctuationEnabled = value;
- REQUEST_RENDER("propsApply floating punct")
+ if ( gHangingPunctuationEnabled != value ) {
+ gHangingPunctuationEnabled = value;
+ REQUEST_RENDER("propsApply - hanging punctuation")
+ // requestRender() does m_doc->clearRendBlockCache(), which is needed
+ // on hanging punctuation change
}
} else if (name == PROP_RENDER_BLOCK_RENDERING_FLAGS) {
int value = props->getIntDef(PROP_RENDER_BLOCK_RENDERING_FLAGS, DEF_RENDER_BLOCK_RENDERING_FLAGS);
diff --git a/crengine/src/lvrend.cpp b/crengine/src/lvrend.cpp
index 2cb46a7eb..01f937358 100644
--- a/crengine/src/lvrend.cpp
+++ b/crengine/src/lvrend.cpp
@@ -50,6 +50,8 @@
// crengine default used to be "width: 100%", but now that we
// can shrink to fit, it is "width: auto".
+bool gHangingPunctuationEnabled = false;
+
int gInterlineScaleFactor = INTERLINE_SCALE_FACTOR_NO_SCALE;
int gRenderDPI = DEF_RENDER_DPI; // if 0: old crengine behaviour: 1px/pt=1px, 1in/cm/pc...=0px
@@ -1460,8 +1462,10 @@ class CCRTable {
LFormattedTextRef txform;
int em = cell->elem->getFont()->getSize();
css_style_ref_t elem_style = cell->elem->getStyle();
- int padding_left = lengthToPx( elem_style->padding[0], cell->width, em ) + measureBorder(cell->elem,3);
- int padding_right = lengthToPx( elem_style->padding[1], cell->width, em ) + measureBorder(cell->elem,1);
+ int border_left = measureBorder(cell->elem,3);
+ int border_right = measureBorder(cell->elem,1);
+ int padding_left = lengthToPx( elem_style->padding[0], cell->width, em ) + border_left;
+ int padding_right = lengthToPx( elem_style->padding[1], cell->width, em ) + border_right;
int padding_top = lengthToPx( elem_style->padding[2], cell->width, em ) + measureBorder(cell->elem,0);
int padding_bottom = lengthToPx( elem_style->padding[3], cell->width, em ) + measureBorder(cell->elem,2);
RenderRectAccessor fmt( cell->elem );
@@ -1473,6 +1477,8 @@ class CCRTable {
fmt.setInnerX( padding_left );
fmt.setInnerY( padding_top );
fmt.setInnerWidth( cell->width - padding_left - padding_right );
+ fmt.setUsableLeftOverflow( padding_left - border_left );
+ fmt.setUsableRightOverflow( padding_right - border_right );
RENDER_RECT_SET_FLAG(fmt, INNER_FIELDS_SET);
RENDER_RECT_SET_DIRECTION(fmt, cell->direction);
fmt.setLangNodeIndex( TextLangMan::getLangNodeIndex(cell->elem) );
@@ -1592,6 +1598,7 @@ class CCRTable {
baseline = REQ_BASELINE_FOR_TABLE;
}
int h = renderBlockElement( *cell_context, cell->elem, 0, 0, cell->width,
+ 0, 0, // no usable left/right overflow outside cell
cell->direction, &baseline, rend_flags);
cell->height = h;
if ( cell->valign == 0 ) { // vertical-align: baseline
@@ -2222,7 +2229,8 @@ lUInt32 styleToTextFmtFlags( bool is_block, const css_style_ref_t & style, lUInt
flg |= LTEXT_FLAG_PREFORMATTED;
if ( style->white_space == css_ws_nowrap ) // white-space: nowrap
flg |= LTEXT_FLAG_NOWRAP;
- //flg |= oldflags & ~LTEXT_FLAG_NEWLINE;
+ if ( STYLE_HAS_CR_HINT(style, FIT_GLYPHS) ) // glyph fitting via -cr-hint
+ flg |= LTEXT_FIT_GLYPHS;
return flg;
}
@@ -2630,6 +2638,8 @@ void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAcce
// it is set to a negative value (the width of the marker), so to handle text
// indentation from the outside marker just like regular negative text-indent.
// So, sadly, let's keep it that way to not break legacy rendering.
+ // todo: pass indent via txform->setTextIndent() (like we do for the strut
+ // below, and get rid of it in AddSourceLine())
indent = lengthToPx(style->text_indent, width, em);
// lvstsheet sets the lowest bit to 1 when text-indent has the "hanging" keyword:
if ( style->text_indent.value & 0x00000001 ) {
@@ -4184,6 +4194,7 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int
fmt.setX( fmt.getX() );
fmt.setY( fmt.getY() );
fmt.setLangNodeIndex( 0 ); // No support for lang in legacy rendering
+ // (No support for overflows and hanging punctuation in legacy mode)
fmt.push();
//if ( CRLog::isTraceEnabled() )
// CRLog::trace("rendering final node: %s %d %s", LCSTR(enode->getNodeName()), enode->getDataIndex(), LCSTR(ldomXPointer(enode,0).toString()) );
@@ -4415,25 +4426,31 @@ class FlowState {
lInt32 lang_node_idx;
int x_min;
int x_max;
+ int usable_overflow_x_min;
+ int usable_overflow_x_max;
int l_y;
int in_y_min;
int in_y_max;
bool avoid_pb_inside;
- void reset(int dir, lInt32 langNodeIdx, int xmin, int xmax, int ly, int iymin, int iymax, bool avoidpbinside) {
+ void reset(int dir, lInt32 langNodeIdx, int xmin, int xmax, int overxmin, int overxmax, int ly, int iymin, int iymax, bool avoidpbinside) {
direction = dir;
lang_node_idx = langNodeIdx;
x_min = xmin;
x_max = xmax;
+ usable_overflow_x_min = overxmin;
+ usable_overflow_x_max = overxmax;
l_y = ly;
in_y_min = iymin;
in_y_max = iymax;
avoid_pb_inside = avoidpbinside;
}
- BlockShift(int dir, lInt32 langNodeIdx, int xmin, int xmax, int ly, int iymin, int iymax, bool avoidpbinside) :
+ BlockShift(int dir, lInt32 langNodeIdx, int xmin, int xmax, int overxmin, int overxmax, int ly, int iymin, int iymax, bool avoidpbinside) :
direction(dir),
lang_node_idx(langNodeIdx),
x_min(xmin),
x_max(xmax),
+ usable_overflow_x_min(overxmin),
+ usable_overflow_x_max(overxmax),
l_y(ly),
in_y_min(iymin),
in_y_max(iymax),
@@ -4444,16 +4461,32 @@ class FlowState {
public:
ldomNode * node;
int level; // level that owns this float
+ int inward_margin; // inner margin (left margin for right floats, right margin for left floats),
+ // allows knowing how much the main text glyphs and hanging punctuation
+ // can protrude inside this float (we limit that to the first level margin,
+ // not including any additional inner padding or margin)
bool is_right;
bool final_pos; // true if y0/y1 are the final absolute position and this
// 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) :
lvRect(x0,y0,x1,y1),
level(l),
+ inward_margin(0),
is_right(r),
final_pos(f),
node(n)
- { }
+ {
+ if (n && n->getChildCount() > 0) {
+ // The margins were used to position the original
+ // float node in its wrapping floatBox - so get it
+ // back from their relative positions
+ RenderRectAccessor fmt(n->getChildNode(0));
+ if (is_right)
+ inward_margin = fmt.getX();
+ else
+ inward_margin = (x1 - x0) - (fmt.getX() + fmt.getWidth());
+ }
+ }
};
int direction; // flow inline direction (LTR/RTL)
lInt32 lang_node_idx; // dataIndex of nearest upper node with a lang="" attribute (0 if none)
@@ -4473,6 +4506,8 @@ class FlowState {
int in_y_max; // that overflow this level height)
int x_min; // current left min x
int x_max; // current right max x
+ int usable_overflow_x_min; // current left and right x usable for glyph overflows and hanging punctuation,
+ int usable_overflow_x_max; // reset when some border or background color change is met
int baseline_req; // baseline type requested (REQ_BASELINE_FOR_INLINE_BLOCK or REQ_BASELINE_FOR_TABLE)
int baseline_y; // baseline y relative to formatting context top (computed when rendering inline-block/table)
bool baseline_set; // (set to true on first baseline met)
@@ -4496,7 +4531,8 @@ class FlowState {
int vm_back_usable_as_margin; // previously moved vertical space where next margin could be accounted in
public:
- FlowState( LVRendPageContext & ctx, int width, int rendflags, int dir=REND_DIRECTION_UNSET, lInt32 langNodeIdx=0 ):
+ FlowState( LVRendPageContext & ctx, int width, int usable_left_overflow, int usable_right_overflow,
+ int rendflags, int dir=REND_DIRECTION_UNSET, lInt32 langNodeIdx=0 ):
direction(dir),
lang_node_idx(langNodeIdx),
context(ctx),
@@ -4537,6 +4573,8 @@ class FlowState {
}
top_clear_level = is_main_flow ? 1 : 2; // see resetFloatsLevelToTopLevel()
page_height = context.getPageHeight();
+ usable_overflow_x_min = x_min - usable_left_overflow;
+ usable_overflow_x_max = x_max + usable_right_overflow;
}
~FlowState() {
// Shouldn't be needed as these must have been cleared
@@ -4589,6 +4627,12 @@ class FlowState {
bool getAvoidPbInside() {
return avoid_pb_inside;
}
+ int getUsableLeftOverflow() {
+ return x_min - usable_overflow_x_min;
+ }
+ int getUsableRightOverflow() {
+ return usable_overflow_x_max - x_max;
+ }
void setRequestedBaselineType(int baseline_req_type) {
baseline_req = baseline_req_type;
@@ -5320,20 +5364,29 @@ class FlowState {
// Enter/leave a block level: backup/restore some of this FlowState
// fields, and do some housekeeping.
- void newBlockLevel( int width, int d_left, bool avoid_pb, int dir, lInt32 langNodeIdx ) {
+ void newBlockLevel( int width, int d_left, int usable_overflow_reset_left, int usable_overflow_reset_right,
+ bool avoid_pb, int dir, lInt32 langNodeIdx ) {
// Don't new/delete to avoid too many malloc/free, keep and re-use/reset
// the ones already created
if ( _shifts.length() <= level ) {
- _shifts.push( new BlockShift( direction, lang_node_idx, x_min, x_max, l_y, in_y_min, in_y_max, avoid_pb_inside ) );
+ _shifts.push( new BlockShift( direction, lang_node_idx,
+ x_min, x_max, usable_overflow_x_min, usable_overflow_x_max,
+ l_y, in_y_min, in_y_max, avoid_pb_inside ) );
}
else {
- _shifts[level]->reset( direction, lang_node_idx, x_min, x_max, l_y, in_y_min, in_y_max, avoid_pb_inside );
+ _shifts[level]->reset( direction, lang_node_idx,
+ x_min, x_max, usable_overflow_x_min, usable_overflow_x_max,
+ l_y, in_y_min, in_y_max, avoid_pb_inside );
}
direction = dir;
if (langNodeIdx != -1)
lang_node_idx = langNodeIdx;
x_min += d_left;
x_max = x_min + width;
+ if ( usable_overflow_reset_left >= 0 ) // -1 means: don't reset, keep previous level limits
+ usable_overflow_x_min = x_min - usable_overflow_reset_left;
+ if ( usable_overflow_reset_right >= 0 )
+ usable_overflow_x_max = x_max + usable_overflow_reset_right;
l_y = c_y;
in_y_min = c_y;
in_y_max = c_y;
@@ -5355,6 +5408,8 @@ class FlowState {
lang_node_idx = prev->lang_node_idx;
x_min = prev->x_min;
x_max = prev->x_max;
+ usable_overflow_x_min = prev->usable_overflow_x_min;
+ usable_overflow_x_max = prev->usable_overflow_x_max;
l_y = prev->l_y;
in_y_min = in_y_min < prev->in_y_min ? in_y_min : prev->in_y_min; // keep sublevel's one if smaller
in_y_max = in_y_max > prev->in_y_max ? in_y_max : prev->in_y_max; // keep sublevel's one if larger
@@ -5765,6 +5820,7 @@ class FlowState {
footprint.floats[floats_involved][2] = x1 - x0; // width
footprint.floats[floats_involved][3] = y1 - y0; // height
footprint.floats[floats_involved][4] = flt->is_right;
+ footprint.floats[floats_involved][5] = flt->inward_margin;
}
floats_involved++;
}
@@ -5895,7 +5951,17 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFloatIds( ldomNode * node,
floats[floats_cnt][1] = y0; // y
floats[floats_cnt][2] = x1 - x0; // width
floats[floats_cnt][3] = y1 - y0; // height
- floats[floats_cnt][4] = RENDER_RECT_HAS_FLAG(fmt, FLOATBOX_IS_RIGHT); // is_right
+ bool is_right = RENDER_RECT_HAS_FLAG(fmt, FLOATBOX_IS_RIGHT);
+ floats[floats_cnt][4] = is_right;
+ int inward_margin = 0;
+ if ( fbox->getChildCount() > 0 ) {
+ RenderRectAccessor fmt(fbox->getChildNode(0));
+ if ( is_right )
+ inward_margin = fmt.getX();
+ else
+ inward_margin = (x1 - x0) - (fmt.getX() + fmt.getWidth());
+ }
+ floats[floats_cnt][5] = inward_margin;
/* Uncomment for checking reproducible results:
if (x1 < x0) printf("!!!! %d %d %d %d\n", rc.left, rc.right, rc.top, rc.bottom);
if ( bf0!=floats[floats_cnt][0] || bf1!=floats[floats_cnt][1] || bf2!=floats[floats_cnt][2] ||
@@ -5923,6 +5989,9 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width
// which case they'll have no visual impact on the text),
// just so we can clear them when a
// is met.
+ // Note: we give inward_margin=0 with fake floats (we
+ // could compute them, but we would need 2 other slots
+ // in RenderRectAccessor to store them, so let's not).
// Top left rectangle
if ( left_h > 0 ) {
floats[floats_cnt][0] = 0; // x
@@ -5930,6 +5999,7 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width
floats[floats_cnt][2] = left_w; // width
floats[floats_cnt][3] = left_h; // height
floats[floats_cnt][4] = 0; // is_right
+ floats[floats_cnt][5] = 0; // inward_margin
floats_cnt++;
}
// Top right rectangle
@@ -5939,6 +6009,7 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width
floats[floats_cnt][2] = right_w; // width
floats[floats_cnt][3] = right_h; // height
floats[floats_cnt][4] = 1; // is_right
+ floats[floats_cnt][5] = 0; // inward_margin
floats_cnt++;
}
// Dummy 0x0 float for minimal y for next left float
@@ -5948,6 +6019,7 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width
floats[floats_cnt][2] = 0; // width
floats[floats_cnt][3] = 0; // height
floats[floats_cnt][4] = 0; // is_right
+ floats[floats_cnt][5] = 0; // inward_margin
floats_cnt++;
}
// Dummy 0x0 float for minimal y for next right float
@@ -5957,6 +6029,7 @@ void BlockFloatFootprint::generateEmbeddedFloatsFromFootprints( int final_width
floats[floats_cnt][2] = 0; // width
floats[floats_cnt][3] = 0; // height
floats[floats_cnt][4] = 1; // is_right
+ floats[floats_cnt][5] = 0; // inward_margin
floats_cnt++;
}
}
@@ -6140,14 +6213,12 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
int em = enode->getFont()->getSize();
- int padding_left = lengthToPx( style->padding[0], container_width, em )
- + measureBorder(enode, 3) + DEBUG_TREE_DRAW;
- int padding_right = lengthToPx( style->padding[1], container_width, em )
- + measureBorder(enode, 1) + DEBUG_TREE_DRAW;
- int padding_top = lengthToPx( style->padding[2], container_width, em )
- + measureBorder(enode, 0) + DEBUG_TREE_DRAW;
- int padding_bottom = lengthToPx( style->padding[3], container_width, em )
- + measureBorder(enode, 2) + DEBUG_TREE_DRAW;
+ int border_left = measureBorder(enode, 3);
+ int border_right = measureBorder(enode, 1);
+ int padding_left = lengthToPx( style->padding[0], container_width, em ) + border_left + DEBUG_TREE_DRAW;
+ int padding_right = lengthToPx( style->padding[1], container_width, em ) + border_right + DEBUG_TREE_DRAW;
+ int padding_top = lengthToPx( style->padding[2], container_width, em ) + measureBorder(enode, 0) + DEBUG_TREE_DRAW;
+ int padding_bottom = lengthToPx( style->padding[3], container_width, em ) + measureBorder(enode, 2) + DEBUG_TREE_DRAW;
css_length_t css_margin_left = style->margin[0];
css_length_t css_margin_right = style->margin[1];
@@ -6812,11 +6883,32 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
// to renderBlockElement() even if it feels a bit out of place,
// notably in the float positionning code. But it works...
+ // Update left and right overflows (usable by glyphs) if this node
+ // has some background or borders, to be given below to 'flow'.
+ int usable_overflow_reset_left = -1;
+ int usable_overflow_reset_right = -1;
+ if ( style->background_color.type == css_val_color || !style->background_image.empty() ) {
+ // New (or same) background color specified (we assume there is
+ // a color change): avoid glyphs/hanging punctuation from leaking
+ // over the background change.
+ usable_overflow_reset_left = padding_left;
+ usable_overflow_reset_right = padding_right;
+ }
+ // If there's some border, avoid glyphs/hanging punctuation from
+ // leaking on or over the border.
+ if ( border_left ) {
+ usable_overflow_reset_left = padding_left - border_left;
+ }
+ if ( border_right ) {
+ usable_overflow_reset_right = padding_right - border_right;
+ }
+
// Shrink flow state area: children that are float will be
// constrained into this area
// ('width' already had margin_left/_right substracted)
flow->newBlockLevel(width - list_marker_padding - padding_left - padding_right, // width
margin_left + (is_rtl ? 0 : list_marker_padding) + padding_left, // d_left
+ usable_overflow_reset_left, usable_overflow_reset_right,
break_inside==RN_SPLIT_AVOID,
direction,
has_lang_attribute ? enode->getDataIndex() : -1);
@@ -6874,8 +6966,12 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
// padding-left/right) for the flow to correctly position inner floats:
// flow->addFloat() will additionally shift its positionning by the
// child x/y set by this renderBlockElement().
+ // We provide 0,0 as the usable left/right overflows, so no glyph/hanging
+ // punctuation will leak outside the floatBox - but the floatBox contains
+ // the initial float element's margins, which can then be used if it has
+ // no border (if borders, only the padding can be used).
renderBlockElement( alt_context, child, (is_rtl ? 0 : list_marker_padding) + padding_left,
- padding_top, width - list_marker_padding - padding_left - padding_right, direction );
+ padding_top, width - list_marker_padding - padding_left - padding_right, 0, 0, direction );
flow->addFloat(child, child_clear, is_right, flt_vertical_margin);
// Gather footnotes links accumulated by alt_context
lString16Collection * link_ids = alt_context.getLinkIds();
@@ -7158,6 +7254,27 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
fmt.setInnerY( padding_top );
fmt.setInnerWidth( inner_width );
RENDER_RECT_SET_FLAG(fmt, INNER_FIELDS_SET);
+ // Usable overflow for glyphs and hanging punctuation
+ int usable_overflow_left = flow->getUsableLeftOverflow() + margin_left;
+ int usable_overflow_right = flow->getUsableRightOverflow() + margin_right;
+ if ( style->background_color.type == css_val_color || !style->background_image.empty() ) {
+ // New (or same) background color specified (we assume there is
+ // a color change): avoid glyphs/hanging punctuation from leaking
+ // over the background change.
+ usable_overflow_left = padding_left;
+ usable_overflow_right = padding_right;
+ }
+ // If there's some border, avoid glyphs/hanging punctuation from
+ // leaking on or over the border.
+ if ( border_left ) {
+ usable_overflow_left = padding_left - border_left;
+ }
+ if ( border_right ) {
+ usable_overflow_right = padding_right - border_right;
+ }
+ fmt.setUsableLeftOverflow( usable_overflow_left );
+ fmt.setUsableRightOverflow( usable_overflow_right );
+ // Done with updating RenderRectAccessor fields, have them saved
fmt.push();
// (These setInner* needs to be set before creating float_footprint if
// we want to debug/valide floatIds coordinates)
@@ -7381,7 +7498,8 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int
}
// Entry points for rendering the root node, a table cell or a float
-int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction, int * baseline, int rend_flags )
+int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
+ int usable_left_overflow, int usable_right_overflow, int direction, int * baseline, int rend_flags )
{
if ( BLOCK_RENDERING(rend_flags, ENHANCED) ) {
// Create a flow state (aka "block formatting context") for the rendering
@@ -7389,7 +7507,8 @@ int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, in
// (We are called when rendering the root node, and when rendering each float
// met along walking the root node hierarchy - and when meeting a new float
// in a float, etc...)
- FlowState flow( context, width, rend_flags, direction, TextLangMan::getLangNodeIndex(enode) );
+ FlowState flow( context, width, usable_left_overflow, usable_right_overflow, rend_flags,
+ direction, TextLangMan::getLangNodeIndex(enode) );
if (baseline != NULL) {
flow.setRequestedBaselineType(*baseline);
}
@@ -7407,11 +7526,13 @@ int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, in
return renderBlockElementLegacy( context, enode, x, y, width);
}
}
-int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction, int * baseline )
+int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width,
+ int usable_left_overflow, int usable_right_overflow, int direction, int * baseline )
{
// Use global rendering flags
// Note: we're not currently using it with other flags that the global ones.
- return renderBlockElement( context, enode, x, y, width, direction, baseline, gRenderBlockRenderingFlags );
+ return renderBlockElement( context, enode, x, y, width, usable_left_overflow, usable_right_overflow,
+ direction, baseline, gRenderBlockRenderingFlags );
}
//draw border lines,support color,width,all styles, not support border-collapse
@@ -9270,6 +9391,10 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
// Start measurements and recursions:
getRenderedWidths(node, maxWidth, minWidth, direction, ignoreMargin, rendFlags,
curMaxWidth, curWordWidth, collapseNextSpace, lastSpaceWidth, indent, NULL, false, isStartNode);
+ // We took more care with including side bearings into minWidth when considering
+ // single words, than into maxWidth: so trust minWidth if larger than maxWidth.
+ if ( maxWidth < minWidth)
+ maxWidth = minWidth;
}
void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direction, bool ignoreMargin, int rendFlags,
@@ -9889,6 +10014,22 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
bool nowrap = (parent_style->white_space == css_ws_nowrap) || (parent_style->white_space == css_ws_pre);
bool pre = parent_style->white_space >= css_ws_pre;
int space_width_scale_percent = pre ? 100 : parent->getDocument()->getSpaceWidthScalePercent();
+
+ // If fit_glyphs, we'll adjust below each word width with calls to
+ // getLeftSideBearing() and getRightSideBearing(). These should be
+ // called with the exact same parameters as used in lvtextfm.cpp
+ // addLine(). (Previously, we adjusted overflows and underflows on
+ // the left, and only overflows on the right. We now only adjust
+ // overflows on both sides - but don't touch underflows to keep
+ // the text natural alignment.)
+ // bool fit_glyphs = STYLE_HAS_CR_HINT(parent_style, FIT_GLYPHS);
+ //
+ // Best to always measure accounting for overflows: we don't know
+ // what adjusments lvtextfm.cpp AddLine() will do depending on
+ // the usable_left/right_overflows it got.
+ // (Let's keep this easily toggable in case we need it.)
+ #define fit_glyphs true
+
// measure text
const lChar16 * txt = text.c_str();
#ifdef DEBUG_GETRENDEREDWIDTHS
@@ -9898,10 +10039,7 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
#define MAX_TEXT_CHUNK_SIZE 4096
static lUInt16 widths[MAX_TEXT_CHUNK_SIZE+1];
static lUInt8 flags[MAX_TEXT_CHUNK_SIZE+1];
- // We adjust below each word width with calls to getLeftSideBearing()
- // and getRightSideBearing(). These should be called with the exact same
- // parameters as used in lvtextfm.cpp getAdditionalCharWidth() and
- // getAdditionalCharWidthOnLeft().
+
// todo: use fribidi and split measurement at fribidi level change,
// and beware left/right side bearing adjustments...
#if (USE_LIBUNIBREAK==1)
@@ -9957,11 +10095,11 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
collapseNextSpace = true; // ignore next spaces, even if in another node
lastSpaceWidth = pre ? 0 : w; // Don't remove last space width if 'pre'
curMaxWidth += w; // add this space to non-wrap width
- if (curWordWidth > 0) { // there was a word before this space
+ if (fit_glyphs && curWordWidth > 0) { // there was a word before this space
if (start+i > 0) {
// adjust for last word's last char overflow (italic, letter f...)
lChar16 prevc = *(txt + start + i - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
}
}
@@ -9973,31 +10111,33 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
collapseNextSpace = false; // next space should not be ignored
lastSpaceWidth = 0; // no width to take off if we stop with this char
curMaxWidth += w;
- if (curWordWidth > 0) { // there was a word or CJK char before this CJK char
+ if (fit_glyphs && curWordWidth > 0) { // there was a word or CJK char before this CJK char
if (start+i > 0) {
// adjust for last word's last char or previous CJK char right overflow
lChar16 prevc = *(txt + start + i - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
}
}
if (curWordWidth > minWidth) // done with previous word
minWidth = curWordWidth; // longest word found
curWordWidth = w;
- // adjust for leading overflow
- int left_overflow = - font->getLeftSideBearing(c, false, true);
- curWordWidth += left_overflow;
- if (start + i == 0) // at start of text only? (not sure)
- curMaxWidth += left_overflow; // also add it to max width
+ if (fit_glyphs) {
+ // adjust for leading overflow
+ int left_overflow = - font->getLeftSideBearing(c, true);
+ curWordWidth += left_overflow;
+ if (start + i == 0) // at start of text only? (not sure)
+ curMaxWidth += left_overflow; // also add it to max width
+ }
}
}
else if (brk == LINEBREAK_MUSTBREAK) { // \n if pre
// Get done with current word
- if (curWordWidth > 0) { // we end with a word
+ if (fit_glyphs && curWordWidth > 0) { // we end with a word
if (start+i > 0) {
// adjust for last word's last char or previous CJK char right overflow
lChar16 prevc = *(txt + start + i - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
curMaxWidth += right_overflow;
}
@@ -10029,9 +10169,9 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
collapseNextSpace = false; // next space should not be ignored
lastSpaceWidth = 0; // no width to take off if we stop with this char
}
- if (curWordWidth == 0) { // first char of a word
+ if (fit_glyphs && curWordWidth == 0) { // first char of a word
// adjust for leading overflow on first char of a word
- int left_overflow = - font->getLeftSideBearing(c, false, true);
+ int left_overflow = - font->getLeftSideBearing(c, true);
curWordWidth += left_overflow;
if (start + i == 0) // at start of text only? (not sure)
curMaxWidth += left_overflow; // also add it to max width
@@ -10067,11 +10207,11 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
collapseNextSpace = true; // ignore next spaces, even if in another node
lastSpaceWidth = w;
curMaxWidth += w; // add this space to non-wrap width
- if (curWordWidth > 0) { // there was a word before this space
+ if (fit_glyphs && curWordWidth > 0) { // there was a word before this space
if (start+i > 0) {
// adjust for last word's last char overflow (italic, letter f...)
lChar16 prevc = *(txt + start + i - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
}
}
@@ -10083,29 +10223,31 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
collapseNextSpace = false; // next space should not be ignored
lastSpaceWidth = 0; // no width to take off if we stop with this char
curMaxWidth += w;
- if (curWordWidth > 0) { // there was a word or CJK char before this CJK char
+ if (fit_glyphs && curWordWidth > 0) { // there was a word or CJK char before this CJK char
if (start+i > 0) {
// adjust for last word's last char or previous CJK char right overflow
lChar16 prevc = *(txt + start + i - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
}
}
if (curWordWidth > minWidth) // done with previous word
minWidth = curWordWidth; // longest word found
curWordWidth = w;
- // adjust for leading overflow
- int left_overflow = - font->getLeftSideBearing(c, false, true);
- curWordWidth += left_overflow;
- if (start + i == 0) // at start of text only? (not sure)
- curMaxWidth += left_overflow; // also add it to max width
+ if (fit_glyphs) {
+ // adjust for leading overflow
+ int left_overflow = - font->getLeftSideBearing(c, true);
+ curWordWidth += left_overflow;
+ if (start + i == 0) // at start of text only? (not sure)
+ curMaxWidth += left_overflow; // also add it to max width
+ }
}
else { // A char part of a word
collapseNextSpace = false; // next space should not be ignored
lastSpaceWidth = 0; // no width to take off if we stop with this char
- if (curWordWidth == 0) { // first char of a word
+ if (fit_glyphs && curWordWidth == 0) { // first char of a word
// adjust for leading overflow on first char of a word
- int left_overflow = - font->getLeftSideBearing(c, false, true);
+ int left_overflow = - font->getLeftSideBearing(c, true);
curWordWidth += left_overflow;
if (start + i == 0) // at start of text only? (not sure)
curMaxWidth += left_overflow; // also add it to max width
@@ -10125,11 +10267,11 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct
}
#endif // not USE_LIBUNIBREAK==1
if ( chars_measured == len ) { // done with this text node
- if (curWordWidth > 0) { // we end with a word
+ if (fit_glyphs && curWordWidth > 0) { // we end with a word
if (start+len > 0) {
// adjust for word last char right overflow
lChar16 prevc = *(txt + start + len - 1);
- int right_overflow = - font->getRightSideBearing(prevc, true, true);
+ int right_overflow = - font->getRightSideBearing(prevc, true);
curWordWidth += right_overflow;
curMaxWidth += right_overflow; // also add it to max width
}
diff --git a/crengine/src/lvstsheet.cpp b/crengine/src/lvstsheet.cpp
index e0abe3c2e..923ab16df 100644
--- a/crengine/src/lvstsheet.cpp
+++ b/crengine/src/lvstsheet.cpp
@@ -1952,6 +1952,7 @@ bool LVCssDeclaration::parse( const char * &decl, bool higher_importance, lxmlDo
}
else if ( substr_icompare("footnote-inpage", decl) ) hints |= CSS_CR_HINT_FOOTNOTE_INPAGE;
else if ( substr_icompare("strut-confined", decl) ) hints |= CSS_CR_HINT_STRUT_CONFINED;
+ else if ( substr_icompare("fit-glyphs", decl) ) hints |= CSS_CR_HINT_FIT_GLYPHS;
else if ( substr_icompare("text-selection-skip", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_SKIP;
else if ( substr_icompare("text-selection-inline", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_INLINE;
else if ( substr_icompare("text-selection-block", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_BLOCK;
diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp
index 019596b17..238bc257d 100644
--- a/crengine/src/lvtextfm.cpp
+++ b/crengine/src/lvtextfm.cpp
@@ -308,8 +308,6 @@ void lvtextAddSourceObject(
#define DUMMY_IMAGE_SIZE 16
-bool gFlgFloatingPunctuationEnabled = true;
-
void LFormattedText::AddSourceObject(
lUInt32 flags, /* flags */
lInt16 interval, /* line height in screen pixels */
@@ -417,6 +415,9 @@ class LVFormatter {
bool m_no_clear_own_floats;
bool m_allow_strut_confining;
bool m_has_multiple_scripts;
+ int m_usable_left_overflow;
+ int m_usable_right_overflow;
+ bool m_hanging_punctuation;
bool m_indent_first_line_done;
int m_indent_after_first_line;
int m_indent_current;
@@ -465,6 +466,9 @@ class LVFormatter {
m_has_ongoing_float = false;
m_no_clear_own_floats = false;
m_has_multiple_scripts = false;
+ m_usable_left_overflow = 0;
+ m_usable_right_overflow = 0;
+ m_hanging_punctuation = false;
m_specified_para_dir = REND_DIRECTION_UNSET;
#if (USE_FRIBIDI==1)
m_bidi_ctypes = NULL;
@@ -617,7 +621,9 @@ class LVFormatter {
LVRendPageContext alt_context( NULL, m_pbuffer->page_height, false );
// We render the float with the specified direction (from upper dir=), even
// if UNSET (and not with the direction determined by fribidi from the text).
- renderBlockElement( alt_context, node, 0, 0, m_pbuffer->width, m_specified_para_dir );
+ // We provide 0,0 as the usable left/right overflows, so no glyph/hanging
+ // punctuation will leak outside the floatBox.
+ renderBlockElement( alt_context, node, 0, 0, m_pbuffer->width, 0, 0, m_specified_para_dir );
// (renderBlockElement will ensure style->height if requested.)
// Gather footnotes links accumulated by alt_context
// (We only need to gather links in the rendering phase, for
@@ -640,6 +646,17 @@ class LVFormatter {
flt->height = height;
flt->to_position = true;
+ if ( node->getChildCount() > 0 ) {
+ // The margins were used to position the original
+ // float node in its wrapping floatBox - so get it
+ // back from their relative positions
+ RenderRectAccessor cfmt(node->getChildNode(0));
+ if ( flt->is_right )
+ flt->inward_margin = cfmt.getX();
+ else
+ flt->inward_margin = width - (cfmt.getX() + cfmt.getWidth());
+ }
+
// If there are already floats to position, don't position any more for now
if ( !m_has_float_to_position ) {
if ( getNextFloatMinY(flt->clear) == m_y ) {
@@ -786,6 +803,16 @@ class LVFormatter {
int w = getAvailableWidthAtY(m_y, m_pbuffer->strut_height, x);
return w < m_pbuffer->width;
}
+ bool isCurrentLineWithFloatOnLeft() {
+ int x;
+ getAvailableWidthAtY(m_y, m_pbuffer->strut_height, x);
+ return x > 0;
+ }
+ bool isCurrentLineWithFloatOnRight() {
+ int x;
+ int w = getAvailableWidthAtY(m_y, m_pbuffer->strut_height, x);
+ return x + w < m_pbuffer->width;
+ }
void checkOngoingFloat() {
// Check if there is still some float spanning at current m_y
// If there is, next added line will ensure no page split
@@ -803,6 +830,65 @@ class LVFormatter {
// no need to avoid page split by next line
}
}
+ // We prefer to not use the fully usable left overflow, but keep
+ // a bit of the margin it comes from
+ #define USABLE_OVERFLOW_USABLE_RATIO 0.8
+ // Use this for testing computations and get visually perfect fitting
+ // #define USABLE_OVERFLOW_USABLE_RATIO 1
+ void getCurrentLineUsableOverflows( int & usable_left_overflow, int & usable_right_overflow ) {
+ if (m_pbuffer->floatcount > 0) {
+ // We have left or right floats on this line, that might
+ // make m_usable_left/right_overflow no more relevant.
+ // We'll allow the main text to overflow in these floats'
+ // inward margin (the float element content itself is also
+ // allowed to overflow in it, so its margin is shared;
+ // hopefully, both overflowing in it at the same position
+ // will be rare).
+ // Note that if the float that sets the text min or max x
+ // have some large inward margin, an other further float
+ // with less inward margin might be the one that should
+ // limit the usable overflow.
+ int fl_left_max_x = 0;
+ int fl_left_max_x_overflow = - m_usable_left_overflow;
+ int fl_right_min_x = m_pbuffer->width;
+ int fl_right_min_x_overflow = m_pbuffer->width + m_usable_right_overflow;
+ // We need to scan pixel line by pixel line along the strut height to be sure
+ int y = m_y;
+ int end_y = y + m_pbuffer->strut_height;
+ while (y <= end_y) {
+ for (int i=0; ifloatcount; i++) {
+ embedded_float_t * flt = m_pbuffer->floats[i];
+ if (flt->to_position) // ignore not yet positionned floats
+ continue;
+ if (flt->y <= y && flt->y + flt->height > y) { // this float is spanning this y
+ if (flt->is_right) {
+ if (flt->x < fl_right_min_x)
+ fl_right_min_x = flt->x;
+ if (flt->x + flt->inward_margin < fl_right_min_x_overflow)
+ fl_right_min_x_overflow = flt->x + flt->inward_margin;
+ // (inward_margin is the left margin of a right float)
+ }
+ else {
+ if (flt->x + flt->width > fl_left_max_x)
+ fl_left_max_x = flt->x + flt->width;
+ if (flt->x + flt->width - flt->inward_margin > fl_left_max_x_overflow)
+ fl_left_max_x_overflow = flt->x + flt->width - flt->inward_margin;
+ // (inward_margin is the right margin of a left float)
+ }
+ }
+ }
+ y += 1;
+ }
+ usable_left_overflow = fl_left_max_x - fl_left_max_x_overflow;
+ usable_right_overflow = fl_right_min_x_overflow - fl_right_min_x;
+ }
+ else {
+ usable_left_overflow = m_usable_left_overflow;
+ usable_right_overflow = m_usable_right_overflow;
+ }
+ usable_left_overflow = usable_left_overflow * USABLE_OVERFLOW_USABLE_RATIO;
+ usable_right_overflow = usable_right_overflow * USABLE_OVERFLOW_USABLE_RATIO;
+ }
/// allocate buffers for paragraph
void allocate( int start, int end )
@@ -1565,44 +1651,6 @@ class LVFormatter {
}
}
- /// checks whether to add more space after italic character
- /// (this could be used to shift some regular font glyphs
- /// like 'f' that often overflows the glyph - but we let
- /// such glyphs overflow in the padding/margin, as it is
- /// quite small and possibly intended by the font designer;
- /// italic overflows are often larger, and need to be
- /// corrected at end of line or end of italic node)
- int getAdditionalCharWidth( int pos, int maxpos ) {
- if (m_text[pos]==0) // object
- return 0; // no additional space
- LVFont * font = (LVFont*)m_srcs[pos]->t.font;
- if (!font)
- return 0; // no font
- if ( posgetRightSideBearing(m_text[pos], true, true);
- // if (glyph_overflow > 0) printf("right overflow: %c %d\n", m_text[pos], glyph_overflow);
- return glyph_overflow;
- }
-
- /// checks whether to add more space on left before italic character
- /// (this could be used to shift some regular font glyphs
- /// like 'J' whose foot often overflows the glyph - but we let
- /// such glyphs overflow in the padding/margin, as it is
- /// quite small and possibly intended by the font designer;
- /// italic underflows and overflows are often larger, and need
- /// to be corrected at start of line)
- int getAdditionalCharWidthOnLeft( int pos ) {
- if (m_text[pos]==0) // object
- return 0; // no additional space
- LVFont * font = (LVFont*)m_srcs[pos]->t.font;
- // Correct italic_only, including removal of positive leading space,
- int glyph_overflow = - font->getLeftSideBearing(m_text[pos], false, true);
- // if (glyph_overflow != 0) printf("left overflow %c: %d\n", m_text[pos], glyph_overflow);
- return glyph_overflow;
- }
-
/// measure word
bool measureWord(formatted_word_t * word, int & width)
{
@@ -1792,7 +1840,7 @@ class LVFormatter {
if ( chars_measured w=%d\n", m_widths[start + k]);
}
+ /* If the following was ever needed, it was wrong to do it at this step
+ * of measureText(), as we then get additional fixed spacing that we may
+ * not need in some contexts. So don't do it: browsers do not.
+ * We'll handle that if LTEXT_FIT_GLYPHS when positionning words
+ * (not implemented for now.)
+
+ // This checks whether we're the last char of a text node, and if
+ // this node is italic, it adds the glyph italic overflow to the
+ // last char width.
+ // This might not be needed if the next text node is also italic,
+ // or if there is a space at start of next text node, and it might
+ // be needed at start of node too as the italic can overflow there too.
+ // It might also confuse our adjustment at start or end of line.
int dw = getAdditionalCharWidth(i-1, m_length);
if ( lastDirection < 0 ) // ignore it for RTL (as right side bearing is measured)
dw = 0;
@@ -1893,12 +1954,9 @@ class LVFormatter {
m_widths[i-1] += dw;
lastWidth += dw;
}
+ */
lastWidth += widths[len-1]; //lenwidth, m_specified_para_dir, &baseline );
+ // We provide 0,0 as the usable left/right overflows, so no glyph/hanging
+ // punctuation will leak outside the inlineBox (we might provide the widths
+ // of any blank space on either side, but here is too early as it might be
+ // shuffled by BiDi reordering.)
+ renderBlockElement( alt_context, node, 0, 0, m_pbuffer->width, 0, 0, m_specified_para_dir, &baseline );
// (renderBlockElement will ensure style->height if requested.)
// Note: this inline box we just rendered can have some overflow
@@ -2283,19 +2345,42 @@ class LVFormatter {
}
/// split line into words, add space for width alignment
- void addLine( int start, int end, int x, src_text_fragment_t * para, bool first, bool last, bool preFormattedOnly, bool isLastPara )
+ void addLine( int start, int end, int x, src_text_fragment_t * para, bool first, bool last, bool preFormattedOnly, bool isLastPara, bool hasInlineBoxes )
{
- int maxWidth = getCurrentLineWidth();
- // provided x is left indent
+ // No need to do some x-alignment work if light formatting, when we
+ // are only interested in computing block height and positionning
+ // floats: 'is_reusable' will be unset, and any attempt at reusing
+ // this formatting for drawing will cause a non-light re-formatting.
+ // Except when there are inlineBoxes in the text: we need to correctly
+ // position them to have their x/y saved in their RenderRectAccessor
+ // (so getRect() can work accurately before the page is drawn).
+ bool light_formatting = m_pbuffer->light_formatting && !hasInlineBoxes;
+
+ // todo: we can avoid some more work below when light_formatting (and
+ // possibly the BiDi re-ordering we need for ordering footnotes, as
+ // if we don't re-order, we'll always have them in the logical order,
+ // and we can just append them in lvrend.cpp instead of checking
+ // where to insert them if RTL - but we'd still have to do that
+ // if some inlinebox prevent doing light formatting :(.)
+
+ // int maxWidth = getCurrentLineWidth(); // if needed for debug printf() below
+
+ // Provided x is the line indent: as we're making words in the visual
+ // order here, it will be line start x for LTR paragraphs; but for RTL
+ // ones, we'll handle it as some reserved space on the right.
int rightIndent = 0;
if ( m_para_dir_is_rtl ) {
rightIndent = x;
- maxWidth -= x; // put x/first char indent on the right: reduce width
+ // maxWidth -= x; // put x/first char indent on the right: reduce width
x = getCurrentLineX(); // use shift induced by left floats
}
else {
x += getCurrentLineX(); // add shift induced by left floats
}
+ // Get overflows, needed to position first and last words
+ int usable_left_overflow;
+ int usable_right_overflow;
+ getCurrentLineUsableOverflows(usable_left_overflow, usable_right_overflow);
// Find out text alignment to ensure for this line
int align = para->flags & LTEXT_FLAG_NEWLINE;
@@ -2335,10 +2420,6 @@ class LVFormatter {
TR("addLine(%d, %d) y=%d align=%d", start, end, m_y, align);
// printf("addLine(%d, %d) y=%d align=%d maxWidth=%d\n", start, end, m_y, align, maxWidth);
- // Note: in the code and comments, all these mean the same thing:
- // visual alignment enabled, floating punctuation, hanging punctuation
- bool visualAlignmentEnabled = (gFlgFloatingPunctuationEnabled != 0) && (align != LTEXT_ALIGN_CENTER);
-
// Note: parameter needReduceSpace and variable splitBySpaces (which
// was always true) have been removed, as we always split by space:
// even if we end up not changing spaces' widths, we need to make
@@ -2598,13 +2679,12 @@ class LVFormatter {
// Some words vertical-align positionning might need to be fixed
// only once the whole line has been laid out
bool delayed_valign_computation = false;
- // alignLine() will have more work to do if we have inlineBox elements
- bool has_inline_boxes = false;
// Make out words, making a new one when some properties change
int wstart = start;
- bool lastIsSpace = false;
+ bool firstWord = true;
bool lastWord = false;
+ bool lastIsSpace = false;
bool isSpace = false;
bool space = false;
// Bidi
@@ -2693,7 +2773,7 @@ class LVFormatter {
// Note: a "word" in our current context is just a unit of text that
// should be rendered together, and can be moved on the x-axis for
// alignment purpose (the 2 french words "qu'autrefois" make a
- // single "word" here, the single word "quelconque", if hyphentaded
+ // single "word" here, the single word "quelconque", if hyphenated
// as "quel-conque" will make one "word" on this line and another
// "word" on the next line.
//
@@ -2763,7 +2843,7 @@ class LVFormatter {
// block cut by
): most browsers don't display the line break
// implied by the BR when we have: "some text
more text"
// or "some text
more text".
- if (lastWord && frmline->word_count == 0) {
+ if (lastWord && firstWord) {
if (!isLastPara) {
wstart--; // make a single word with a single collapsed space
if (m_flags[wstart] & LCHAR_IS_TO_IGNORE) {
@@ -2820,7 +2900,6 @@ class LVFormatter {
word->min_width = word->width;
word->o.height = srcline->o.height;
if ( srcline->flags & LTEXT_SRC_IS_INLINE_BOX ) { // inline-block
- has_inline_boxes = true;
word->flags = LTEXT_WORD_IS_INLINE_BOX;
// For inline-block boxes, the baseline may not be the bottom; it has
// been computed in measureText().
@@ -2974,14 +3053,6 @@ class LVFormatter {
// printf("baseline_to_bottom=%d top_to_baseline=%d word->y=%d txt=|%s|\n", baseline_to_bottom,
// top_to_baseline, word->y, UnicodeToLocal(lString16(srcline->t.text, srcline->t.len)).c_str());
- // For Harfbuzz, which may shape differently words at start or end of paragraph
- if (first && frmline->word_count == 1) // first line of paragraph + first word of line
- word->flags |= LTEXT_WORD_BEGINS_PARAGRAPH;
- if (last && lastWord) // last line of paragraph + last word of line
- word->flags |= LTEXT_WORD_ENDS_PARAGRAPH;
- if ( trustDirection)
- word->flags |= LTEXT_WORD_DIRECTION_KNOWN;
-
// Set word start and end (start+len-1) indices in the source text node
if ( !m_has_bidi ) {
// No bidi, everything is linear
@@ -3019,6 +3090,29 @@ class LVFormatter {
word->t.len = m_charindex[i-1] + 1 - m_charindex[wstart];
}
+ // Flag word that are the start of a link (for in-page footnotes)
+ if ( word->t.start==0 && srcline->flags & LTEXT_IS_LINK ) {
+ word->flags |= LTEXT_WORD_IS_LINK_START;
+ // todo: we might miss some links if the source text starts with a space
+ }
+
+ // Below this are stuff that could be skipped if light_formatting
+ // (We need bidi and the above adjustment only to get correctly ordered
+ // in-page footnotes links.)
+
+ // For Harfbuzz, which may shape differently words at start or end of paragraph.
+ // todo: this is probably wrong if some multi bidi levels re-ordering has been done
+ if ( first ) { // first line of paragraph
+ if ( m_para_dir_is_rtl ? lastWord : firstWord )
+ word->flags |= LTEXT_WORD_BEGINS_PARAGRAPH;
+ }
+ if ( last ) { // last line of paragraph
+ if ( m_para_dir_is_rtl ? firstWord : lastWord )
+ word->flags |= LTEXT_WORD_ENDS_PARAGRAPH;
+ }
+ if ( trustDirection)
+ word->flags |= LTEXT_WORD_DIRECTION_KNOWN;
+
// We need to compute how many glyphs can have letter_spacing added, that
// might be done in alignLine() (or not). We have to do it now even if
// not used, as we won't have that information anymore in alignLine().
@@ -3050,6 +3144,83 @@ class LVFormatter {
word->distinct_glyphs += tailing_spaces;
}
+ // If we're asked to fit glyphs (avoid glyphs from overflowing line edges and
+ // on neighbour text nodes), we might need to tweak words x and width
+ bool fit_glyphs = srcline->flags & LTEXT_FIT_GLYPHS;
+
+ if ( firstWord && (align == LTEXT_ALIGN_LEFT || align == LTEXT_ALIGN_WIDTH) ) {
+ // Adjust line start x if needed
+ // No need to do it when line is centered or right aligned (doing so
+ // might increase the line width and change space widths for no reason).
+ // We currently have no chance to get an added hyphen for hyphenation
+ // at start of line, as we handle only hyphenation with LTR text.
+ // It feels we have to do it even for the first line with text-indent,
+ // as some page might have multiple consecutive single lines that can
+ // benefit from hanging so the margin looks clean too.
+ int lsb = font->getLeftSideBearing(m_text[wstart]);
+ int left_overflow = lsb < 0 ? -lsb : 0;
+ if ( fit_glyphs ) {
+ // We don't want any part of the glyph to overflow in the left margin.
+ // We correct only overflows - keeping underflows (so, not having
+ // the glyph blackbox really fit the edge) respects the natural
+ // alignment.
+ // We also prevent hanging punctuation as it de facto overflows.
+ // (We used to correct it only for italic fonts, where "J" or "f"
+ // can have have huge negative overflow for their part below baseline
+ // and so leak on the left. On the left, we were also correcting
+ // underflows, so fitting italic glyphs to the left edge - but we
+ // don't anymore as it doesn't really feel needed.)
+ frmline->x += left_overflow; // so that the glyph's overflow is at original frmline->x
+ // printf("%c lsb=%d\n", m_text[wstart], font->getLeftSideBearing(m_text[wstart]));
+ }
+ else {
+ // We prevent hanging punctuation on the common opening quotation marks
+ // or dashes that we flagged with LCHAR_LOCKED_SPACING (most of these
+ // are characters that can hang) - and on fully-pre lines and when
+ // the font is monospace.
+ // Note that some CJK fonts might have full-width glyphs for some of our
+ // common hanging chars, but not for others, and this might look bad with
+ // them, and different whether it is used as the main font or as a fallback.
+ // (Noto Sans CJK SC has full-width glyphs for single or double quotation
+ // marks (‘ ’ “ ”), but not for all our other hanging chars.)
+ // Reducing CJK half-blank full-width glyphs's width should be handled
+ // more generically elsewhere.
+ // We try to avoid hanging these with some heuristic below.
+ bool allow_hanging = m_hanging_punctuation &&
+ !preFormattedOnly &&
+ !(m_flags[wstart] & LCHAR_LOCKED_SPACING) &&
+ font->getFontFamily() != css_ff_monospace;
+ int shift_x = 0;
+ if ( allow_hanging ) {
+ bool check_font;
+ int percent = srcline->lang_cfg->getHangingPercent(false, check_font, m_text, wstart, end-wstart-1);
+ if ( percent && check_font && left_overflow > 0 ) {
+ // Some fonts might already have enough negative
+ // left side bearing for some chars, that would
+ // make them naturally hang on the left.
+ percent = 0;
+ }
+ if ( percent ) {
+ int first_char_width = m_widths[wstart] - (wstart>0 ? m_widths[wstart-1] : 0);
+ shift_x = first_char_width * percent / 100;
+ if ( shift_x == 0 ) // Force at least 1px if division rounded it to 0
+ shift_x = 1;
+ // Cancel it if this char looks like it might be full-width
+ // (0.9 * font size, in case HarfBuzz has reduced the advance)
+ // and it has a lot of positive left side bearing (left half
+ // of the glyph blank) - see above.
+ if ( first_char_width > 0.9 * font->getSize() && lsb > 0.4 * first_char_width ) {
+ shift_x = 0;
+ }
+ }
+ }
+ if ( shift_x - lsb > usable_left_overflow ) {
+ shift_x = usable_left_overflow + lsb;
+ }
+ frmline->x -= shift_x;
+ }
+ }
+
// Word x position on line: for now, we just stack words after each other.
// They will be adjusted if needed in alignLine()
word->x = frmline->width;
@@ -3110,7 +3281,7 @@ class LVFormatter {
word->min_width = word->width;
}
}
- else if ( frmline->word_count>1 && m_flags[wstart] & LCHAR_IS_SPACE ) {
+ else if ( !firstWord && m_flags[wstart] & LCHAR_IS_SPACE ) {
// Current word starts with a space (looks like this should not happen):
// we can increase the space between previous word and this one if needed
//if ( word->t.len<2 || m_text[i-1]!=UNICODE_NO_BREAK_SPACE || m_text[i-2]!=UNICODE_NO_BREAK_SPACE)
@@ -3119,17 +3290,99 @@ class LVFormatter {
//else
frmline->words[frmline->word_count-2].flags |= LTEXT_WORD_CAN_ADD_SPACE_AFTER;
}
- else if (frmline->word_count>1 && isCJKIdeograph(m_text[i])) {
+ else if ( !firstWord && isCJKIdeograph(m_text[i]) ) {
// Current word is a CJK char: we can increase the space
// between previous word and this one if needed
frmline->words[frmline->word_count-2].flags |= LTEXT_WORD_CAN_ADD_SPACE_AFTER;
}
-
// if ( m_flags[i-1] & LCHAR_ALLOW_WRAP_AFTER )
// word->flags |= LTEXT_WORD_CAN_BREAK_LINE_AFTER; // not used anywhere
- if ( word->t.start==0 && srcline->flags & LTEXT_IS_LINK )
- word->flags |= LTEXT_WORD_IS_LINK_START; // for in-page footnotes
+ if ( lastWord && (align == LTEXT_ALIGN_RIGHT || align == LTEXT_ALIGN_WIDTH) ) {
+ // Adjust line end if needed.
+ // If we need to adjust last word's last char, we need to put the delta
+ // in this word->width, which will make it into frmline->width.
+
+ // Find the real last drawn glyph
+ int lastnonspace = i-1;
+ for ( int k=i-1; k>=wstart; k-- ) {
+ if ( !(m_flags[k] & LCHAR_IS_SPACE) ) {
+ lastnonspace = k;
+ break;
+ }
+ }
+ bool ends_with_hyphen = m_flags[lastnonspace] & LCHAR_ALLOW_HYPH_WRAP_AFTER;
+ int rsb = 0; // don't bother with hyphen rsb, which can't overflow
+ int right_overflow = 0;
+ if ( !ends_with_hyphen ) {
+ rsb = font->getRightSideBearing(m_text[lastnonspace]);
+ if ( rsb < 0 )
+ right_overflow = -rsb;
+ }
+ if ( fit_glyphs ) {
+ // We don't want any part of the glyph to overflow in the right margin.
+ // (We used to correct it only for italic fonts, where "J" or "f"
+ // can have have huge negative overflow for their part above baseline
+ // and so leak on the right. We were previously also correcting only
+ // overflows and not underflows.)
+ word->width += right_overflow;
+ }
+ else {
+ // We prevent hanging punctuation in a few cases (see above)
+ bool allow_hanging = m_hanging_punctuation &&
+ !preFormattedOnly &&
+ font->getFontFamily() != css_ff_monospace;
+ int shift_w = 0;
+ if ( allow_hanging ) {
+ if ( ends_with_hyphen ) {
+ int percent = srcline->lang_cfg->getHyphenHangingPercent();
+ if ( percent ) {
+ shift_w = font->getHyphenWidth() * percent / 100;
+ if ( shift_w == 0 ) // Force at least 1px if division rounded it to 0
+ shift_w = 1;
+ }
+ // Note: some part of text in bold or in a bigger font size inside
+ // a paragraph may stand out more than the regular text, and this
+ // is quite noticable with the hyphen.
+ // We might want to limit or force hyphen hanging to what it should
+ // be with the main paragraph font, but that might not work well in
+ // some situations.
+ // See https://github.com/koreader/crengine/pull/355#issuecomment-656760791
+ }
+ else {
+ bool check_font;
+ int percent = srcline->lang_cfg->getHangingPercent(true, check_font, m_text, lastnonspace, end-lastnonspace-1);
+ if ( percent && check_font && right_overflow > 0 ) {
+ // Some fonts might already have enough negative
+ // right side bearing for some chars, that would
+ // make them naturally hang on the right.
+ percent = 0;
+ }
+ if ( percent ) {
+ int last_char_width = m_widths[lastnonspace] - (lastnonspace>0 ? m_widths[lastnonspace-1] : 0);
+ shift_w = last_char_width * percent / 100;
+ if ( shift_w == 0 ) // Force at least 1px if division rounded it to 0
+ shift_w = 1;
+ // Cancel it if this char looks like it might be full-width
+ // (0.9 * font size, in case HarfBuzz has reduced the advance)
+ // and it has a lot of positive right side bearing (right half
+ // of the glyph blank) - see comment above in 'firstWord' handling.
+ if ( last_char_width > 0.9 * font->getSize() && rsb > 0.4 * last_char_width ) {
+ shift_w = 0;
+ }
+ }
+ }
+ }
+ if ( shift_w - rsb > usable_right_overflow ) {
+ shift_w = usable_right_overflow + rsb;
+ }
+ word->width -= shift_w;
+ }
+ }
+
+ /* Hanging punctuation (with CJK specifics) old code:
+ *
+ bool visualAlignmentEnabled = m_hanging_punctuation && (align != LTEXT_ALIGN_CENTER);
if ( visualAlignmentEnabled && lastWord ) { // if floating punctuation enabled
int endp = i-1;
int lastc = m_text[endp];
@@ -3224,6 +3477,8 @@ class LVFormatter {
}
word->min_width = word->width;
} // done if floating punctuation enabled
+ * End of old code for handling hanging punctuation
+ */
// printf("addLine - word(%d, %d) x=%d (%d..%d)[%d>%d %x] |%s|\n", wstart, i,
// frmline->width, wstart>0 ? m_widths[wstart-1] : 0, m_widths[i-1], word->width,
@@ -3284,6 +3539,7 @@ class LVFormatter {
}
frmline->width += word->width;
+ firstWord = false;
lastSrc = newSrc;
wstart = i;
@@ -3346,16 +3602,9 @@ class LVFormatter {
}
}
- // Fix up words position and width to ensure requested alignment and indent
- // No need to do that if light formatting, as this won't affect the
- // block height and floats positionning - is_reusable will be unset,
- // and any attempt at reusing this formatting for drawing will cause
- // a non-light re-formatting. Except when there are inlineBoxes in the
- // text: we need to correctly position them to have their x/y saved
- // in their RenderRectAccessor (so getRect() can work accurately before
- // the page is drawn).
- if ( !m_pbuffer->light_formatting || has_inline_boxes ) {
- alignLine( frmline, align, rightIndent, has_inline_boxes );
+ if ( !light_formatting ) {
+ // Fix up words position and width to ensure requested alignment and indent
+ alignLine( frmline, align, rightIndent, hasInlineBoxes );
}
// Get ready for next line
@@ -3396,6 +3645,7 @@ class LVFormatter {
c >= UNICODE_CJK_PUNCTUATION_HALF_AND_FULL_WIDTH_END );
}
+ #if (USE_LIBUNIBREAK!=1)
bool isCJKPunctuation(lChar16 c) {
return ( c >= UNICODE_CJK_PUNCTUATION_BEGIN && c <= UNICODE_CJK_PUNCTUATION_END ) ||
( c >= UNICODE_GENERAL_PUNCTUATION_BEGIN && c <= UNICODE_GENERAL_PUNCTUATION_END &&
@@ -3413,6 +3663,7 @@ class LVFormatter {
c==0x3008 || c==0x300a || c==0x300c || c==0x300e || c==0x3010 || // 〈 《 「 『 【 CJK left brackets
c==0xff08; // ( fullwidth left parenthesis
}
+ #endif
bool isLeftPunctuation(lChar16 c) {
// Opening quotation marks and dashes that we don't want a followup space to
@@ -3460,28 +3711,6 @@ class LVFormatter {
preFormattedOnly = preFormattedOnly && lfFound;
}
- bool visualAlignmentEnabled = gFlgFloatingPunctuationEnabled!=0;
- int visualAlignmentWidth = 0;
- if ( visualAlignmentEnabled ) {
- // We remove from the available width the max of the max width
- // of -/./,/!/? (and other CJK ones) in all fonts used in that
- // paragraph, to reserve room for it in case we get one hanging.
- // (This will lead to messy variable paragraph widths if some
- // paragraph use some bigger font for some inline parts, and
- // others don't.)
- LVFont * font = NULL;
- for ( int i=start; isrctext[i].flags & LTEXT_SRC_IS_OBJECT) ) {
- font = (LVFont*)m_pbuffer->srctext[i].t.font;
- if (font) {
- int dx = font->getVisualAligmentWidth();
- if ( dx>visualAlignmentWidth )
- visualAlignmentWidth = dx;
- }
- }
- }
- }
-
// Not per-specs, but when floats reduce the available width, skip y until
// we have the width to draw at least a few chars on a line.
// We use N x strut_height because it's one easily acccessible font metric here.
@@ -3493,6 +3722,14 @@ class LVFormatter {
int upSkipPos = -1;
#endif
+ // Note: we no longer adjust here x and width to account for first or
+ // last italic glyphs side bearings or hanging punctuation, as here,
+ // we're still just walking the text in logical order, which might
+ // be re-ordered when BiDi.
+ // We'll handle that in AddLine() where we'll make words in visual
+ // order; the small shifts we might have on the final width vs the
+ // width measured here will hopefully be compensated on the space chars.
+
while ( posflags & LTEXT_SRC_IS_FLOAT_DONE) ) {
- int currentWidth = x + firstCharMargin + m_widths[i]-w0 - spaceReduceWidth;
- addFloat( src, currentWidth );
- src->flags |= LTEXT_SRC_IS_FLOAT_DONE;
- maxWidth = getCurrentLineWidth();
+ if ( flags & LCHAR_IS_OBJECT ) {
+ if ( m_charindex[i] == FLOAT_CHAR_INDEX ) { // float
+ src_text_fragment_t * src = m_srcs[i];
+ // Not sure if we can be called again on the same LVFormatter
+ // object, but the whole code allows for re-formatting and
+ // they should give the same result.
+ // So, use a flag to not re-add already processed floats.
+ if ( !(src->flags & LTEXT_SRC_IS_FLOAT_DONE) ) {
+ int currentWidth = x + m_widths[i]-w0 - spaceReduceWidth;
+ addFloat( src, currentWidth );
+ src->flags |= LTEXT_SRC_IS_FLOAT_DONE;
+ maxWidth = getCurrentLineWidth();
+ }
+ // We don't set lastNormalWrap when collapsed spaces,
+ // so let's not for floats either.
+ // But we need to when the float is the last source (as
+ // done below, otherwise we would not update wrapPos and
+ // we'd get another ghost line, and this real last line
+ // might be wrongly justified).
+ if ( i==m_length-1 ) {
+ lastNormalWrap = i;
+ }
+ continue;
}
- // We don't set lastNormalWrap when collapsed spaces,
- // so let's not for floats either.
- // But we need to when the float is the last source (as
- // done below, otherwise we would not update wrapPos and
- // we'd get another ghost line, and this real last line
- // might be wrongly justified).
- if ( i==m_length-1 ) {
- lastNormalWrap = i;
+ if ( m_charindex[i] == INLINEBOX_CHAR_INDEX && firstInlineBoxPos < 0 ) {
+ firstInlineBoxPos = i;
}
- continue;
}
// We would not need to bother with LCHAR_IS_COLLAPSED_SPACE, as they have zero
// width and so can be grabbed here. They carry LCHAR_ALLOW_WRAP_AFTER just like
@@ -3587,39 +3827,10 @@ class LVFormatter {
fillAndMoveToY( new_y );
maxWidth = getCurrentLineWidth();
}
- // Shift first italic glyph whose part below baseline might leak on the left.
- // Note: we might not need to bother with negative left side bearing, as we
- // now can have them in the margin as we don't clip anymore. So, we could
- // just have italic "J" or "f" drawn a bit in the margin.
- // But shifting them that way makes for nicer multilines higlighting boxes.
- firstCharMargin = getAdditionalCharWidthOnLeft(pos);
- if ( visualAlignmentEnabled ) { // Floating punctuation
- maxWidth -= visualAlignmentWidth;
- spaceReduceWidth -= visualAlignmentWidth/2;
- firstCharMargin += visualAlignmentWidth/2;
- if (isCJKLeftPunctuation(m_text[pos])) {
- // Make that left punctuation left-hanging by reducing firstCharMargin
- LVFont * fnt = (LVFont *)m_srcs[pos]->t.font;
- if (fnt)
- firstCharMargin -= fnt->getCharWidth(m_text[pos]);
- firstCharMargin = (x + firstCharMargin) > 0 ? firstCharMargin : 0;
- }
- }
- if ( m_has_bidi ) {
- // If bidi, our first char may be no more the first char
- // inside AddLine, so reset firtCharMargin to 0.
- // (a bit sad to do that if there's a single RTL word
- // in the middle of line...)
- firstCharMargin = 0;
- // todo: probably some other things to avoid if bidi or
- // if m_para_dir_is_rtl, like hyphenation.
- // Also possible: scan chars as they fit on this line for
- // bidi level > 1: if none, this line is pure LTR
- }
}
bool grabbedExceedingSpace = false;
- if ( x + firstCharMargin + m_widths[i]-w0 > maxWidth + spaceReduceWidth ) {
+ if ( x + m_widths[i]-w0 > maxWidth + spaceReduceWidth ) {
// It's possible the char at i is a space whose width exceeds maxWidth,
// but it should be a candidate for lastNormalWrap (otherwise, the
// previous word will be hyphenated and we will get spaces widen for
@@ -3703,11 +3914,12 @@ class LVFormatter {
m_pbuffer->min_space_condensing_percent != 100 &&
i < m_length-1 &&
( m_flags[i] & LCHAR_IS_SPACE ) &&
- ( i==m_length-1 || !(m_flags[i + 1] & LCHAR_IS_SPACE) ) ) {
+ !(m_flags[i+1] & LCHAR_IS_SPACE) ) {
// Each space not followed by a space is candidate for space condensing
int dw = getMaxCondensedSpaceTruncation(i);
if ( dw>0 )
spaceReduceWidth += dw;
+ // TODO do that too for CJK punctuation whose glyph might be half blank
}
if (grabbedExceedingSpace)
break; // delayed break
@@ -3735,9 +3947,6 @@ class LVFormatter {
#endif
int normalWrapWidth = lastNormalWrap > 0 ? x + m_widths[lastNormalWrap]-w0 : 0;
int unusedSpace = maxWidth - normalWrapWidth;
- if ( visualAlignmentEnabled ) {
- unusedSpace -= 2*visualAlignmentWidth;
- }
int unusedPercent = maxWidth > 0 ? unusedSpace * 100 / maxWidth : 0;
#if (USE_LIBUNIBREAK!=1)
// (Different usage of deprecatedWrap than above)
@@ -3825,7 +4034,7 @@ class LVFormatter {
for ( int i=0; i 0 && !(m_flags[endp-1] & LCHAR_ALLOW_HYPH_WRAP_AFTER) ) {
- // Find the real last displayed glyph, skipping spaces and floats
- int lastnonspace = endp-1;
- for ( int k=endp-1; k>=start; k-- ) {
- if ( !(m_flags[k] & LCHAR_IS_SPACE) &&
- !( (m_flags[k] & LCHAR_IS_OBJECT) && (m_charindex[k] == FLOAT_CHAR_INDEX) ) ) {
- lastnonspace = k;
- break;
- }
- }
- // If the last non-space/non-float is an image or an inline-block box, we don't do it.
- // Note: it feels we should do that for the char before ANY image on the line (so the italic
- // glyph does not overlap with the image). It's unclear whether the former code did that
- // (or not) for the char before an image at end of line only...
- if ( !(m_flags[lastnonspace] & LCHAR_IS_OBJECT) ) {
- // todo: probably need be avoided if bidi/rtl:
- int dw = lastnonspace>=start ? getAdditionalCharWidth(lastnonspace, lastnonspace+1) : 0;
- if (dw) {
- TR("additional width = %d, after char %s", dw, LCSTR(lString16(m_text + lastnonspace, 1)));
- m_widths[lastnonspace] += dw;
- }
- }
- }
-
- addLine(pos, endp, x + firstCharMargin, para, pos==0, wrapPos>=m_length-1, preFormattedOnly, isLastPara);
+ bool hasInlineBoxes = firstInlineBoxPos >= 0 && firstInlineBoxPos < endp;
+ addLine(pos, endp, x, para, pos==0, wrapPos>=m_length-1, preFormattedOnly, isLastPara, hasInlineBoxes);
pos = wrapPos + 1; // start of next line
#if (USE_LIBUNIBREAK==1)
@@ -4030,7 +4216,13 @@ class LVFormatter {
// that, and we could get some small mismatches and glitches.
rend_flags &= ~BLOCK_RENDERING_ALLOW_NEGATIVE_COLLAPSED_MARGINS;
int baseline = REQ_BASELINE_FOR_TABLE; // baseline of block is baseline of its first line
- renderBlockElement( context, node, 0, 0, width, m_specified_para_dir, &baseline, rend_flags);
+ // The same usable overflows provided for the container (possibly
+ // adjusted for floats) can be used for this full-width inlineBox.
+ int usable_left_overflow;
+ int usable_right_overflow;
+ getCurrentLineUsableOverflows(usable_left_overflow, usable_right_overflow);
+ renderBlockElement( context, node, 0, 0, width, usable_left_overflow, usable_right_overflow,
+ m_specified_para_dir, &baseline, rend_flags);
RenderRectAccessor fmt( node );
fmt.setX(block_x);
fmt.setY(m_y);
@@ -4223,7 +4415,9 @@ static void freeFrmLines( formatted_text_fragment_t * m_pbuffer )
}
// experimental formatter
-lUInt32 LFormattedText::Format(lUInt16 width, lUInt16 page_height, int para_direction, BlockFloatFootprint * float_footprint)
+lUInt32 LFormattedText::Format(lUInt16 width, lUInt16 page_height, int para_direction,
+ int usable_left_overflow, int usable_right_overflow, bool hanging_punctuation,
+ BlockFloatFootprint * float_footprint)
{
// clear existing formatted data, if any
freeFrmLines( m_pbuffer );
@@ -4255,6 +4449,10 @@ lUInt32 LFormattedText::Format(lUInt16 width, lUInt16 page_height, int para_dire
// it will be detected by fribidi)
formatter.m_specified_para_dir = para_direction;
+ formatter.m_usable_left_overflow = usable_left_overflow;
+ formatter.m_usable_right_overflow = usable_right_overflow;
+ formatter.m_hanging_punctuation = hanging_punctuation;
+
if (float_footprint) {
formatter.m_no_clear_own_floats = float_footprint->no_clear_own_floats;
@@ -4273,6 +4471,7 @@ lUInt32 LFormattedText::Format(lUInt16 width, lUInt16 page_height, int para_dire
flt->width = float_footprint->floats[i][2];
flt->height = float_footprint->floats[i][3];
flt->is_right = (bool)(float_footprint->floats[i][4]);
+ flt->inward_margin = float_footprint->floats[i][5];
}
}
diff --git a/crengine/src/lvtinydom.cpp b/crengine/src/lvtinydom.cpp
index c9b3647e0..d96b53375 100644
--- a/crengine/src/lvtinydom.cpp
+++ b/crengine/src/lvtinydom.cpp
@@ -84,7 +84,7 @@ int gDOMVersionRequested = DOM_VERSION_CURRENT;
/// change in case of incompatible changes in swap/cache file format to avoid using incompatible swap file
// increment to force complete reload/reparsing of old file
-#define CACHE_FILE_FORMAT_VERSION "3.05.44k"
+#define CACHE_FILE_FORMAT_VERSION "3.05.45k"
/// increment following value to force re-formatting of old book after load
#define FORMATTING_VERSION_ID 0x0025
@@ -390,8 +390,11 @@ lUInt32 calcGlobalSettingsHash(int documentId, bool already_rendered)
// hash = hash * 31 + (int)fontMan->GetHintingMode();
if ( LVRendGetFontEmbolden() )
hash = hash * 75 + 2384761;
- if ( gFlgFloatingPunctuationEnabled )
- hash = hash * 75 + 1761;
+ // Hanging punctuation does not need to trigger a re-render, as
+ // it's now ensured by alignLine() and won't change paragraphs height.
+ // We just need to _renderedBlockCache.clear() when it changes.
+ // if ( gHangingPunctuationEnabled )
+ // hash = hash * 75 + 1761;
hash = hash * 31 + gRenderDPI;
hash = hash * 31 + gRenderBlockRenderingFlags;
hash = hash * 31 + gRootFontSize;
@@ -1617,6 +1620,58 @@ int RenderRectAccessor::getInnerWidth()
}
return _inner_width;
}
+int RenderRectAccessor::getUsableLeftOverflow()
+{
+ if ( _dirty ) {
+ _dirty = false;
+ _node->getRenderData(*this);
+#ifdef DEBUG_RENDER_RECT_ACCESS
+ rr_lock( _node );
+#endif
+ }
+ return _usable_left_overflow;
+}
+int RenderRectAccessor::getUsableRightOverflow()
+{
+ if ( _dirty ) {
+ _dirty = false;
+ _node->getRenderData(*this);
+#ifdef DEBUG_RENDER_RECT_ACCESS
+ rr_lock( _node );
+#endif
+ }
+ return _usable_right_overflow;
+}
+void RenderRectAccessor::setUsableLeftOverflow( int dx )
+{
+ if ( _dirty ) {
+ _dirty = false;
+ _node->getRenderData(*this);
+#ifdef DEBUG_RENDER_RECT_ACCESS
+ rr_lock( _node );
+#endif
+ }
+ if ( dx < 0 ) dx = 0; // don't allow a negative value
+ if ( _usable_left_overflow != dx ) {
+ _usable_left_overflow = dx;
+ _modified = true;
+ }
+}
+void RenderRectAccessor::setUsableRightOverflow( int dx )
+{
+ if ( _dirty ) {
+ _dirty = false;
+ _node->getRenderData(*this);
+#ifdef DEBUG_RENDER_RECT_ACCESS
+ rr_lock( _node );
+#endif
+ }
+ if ( dx < 0 ) dx = 0; // don't allow a negative value
+ if ( _usable_right_overflow != dx ) {
+ _usable_right_overflow = dx;
+ _modified = true;
+ }
+}
int RenderRectAccessor::getTopOverflow()
{
if ( _dirty ) {
@@ -4519,7 +4574,9 @@ bool ldomDocument::parseStyleSheet(lString16 cssFile)
return parser.Parse(cssFile);
}
-bool ldomDocument::render( LVRendPageList * pages, LVDocViewCallback * callback, int width, int dy, bool showCover, int y0, font_ref_t def_font, int def_interline_space, CRPropRef props )
+bool ldomDocument::render( LVRendPageList * pages, LVDocViewCallback * callback, int width, int dy,
+ bool showCover, int y0, font_ref_t def_font, int def_interline_space,
+ CRPropRef props, int usable_left_overflow, int usable_right_overflow )
{
CRLog::info("Render is called for width %d, pageHeight=%d, fontFace=%s, docFlags=%d", width, dy, def_font->getTypeFace().c_str(), getDocFlags() );
CRLog::trace("initializing default style...");
@@ -4627,7 +4684,7 @@ bool ldomDocument::render( LVRendPageList * pages, LVDocViewCallback * callback,
context.setCallback(callback, numFinalBlocks);
//updateStyles();
CRLog::trace("rendering...");
- renderBlockElement( context, getRootNode(), 0, y0, width );
+ renderBlockElement( context, getRootNode(), 0, y0, width, usable_left_overflow, usable_right_overflow );
_rendered = true;
#if 0 //def _DEBUG
LVStreamRef ostream = LVOpenFileStream( "test_save_after_init_rend_method.xml", LVOM_WRITE );
@@ -8784,13 +8841,9 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const
int chw = w[ offset - word->t.start ] - chx;
bool hyphen_added = false;
if ( offset == word->t.start + word->t.len - 1
- && (word->flags & LTEXT_WORD_CAN_HYPH_BREAK_LINE_AFTER)
- && !gFlgFloatingPunctuationEnabled ) {
+ && (word->flags & LTEXT_WORD_CAN_HYPH_BREAK_LINE_AFTER) ) {
// if offset is the end of word, and this word has
// been hyphenated, includes the hyphen width
- // (but not when floating punctuation is enabled,
- // to keep nice looking rectangles on multi lines
- // text selection)
chw += font->getHyphenWidth();
// We then should not account for the right side
// bearing below
@@ -8955,13 +9008,9 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const
int chw = w[ offset - word->t.start ] - chx;
bool hyphen_added = false;
if ( offset == word->t.start + word->t.len - 1
- && (word->flags & LTEXT_WORD_CAN_HYPH_BREAK_LINE_AFTER)
- && !gFlgFloatingPunctuationEnabled ) {
+ && (word->flags & LTEXT_WORD_CAN_HYPH_BREAK_LINE_AFTER) ) {
// if offset is the end of word, and this word has
// been hyphenated, includes the hyphen width
- // (but not when floating punctuation is enabled,
- // to keep nice looking rectangles on multi lines
- // text selection)
chw += font->getHyphenWidth();
// We then should not account for the right side
// bearing below
@@ -17146,22 +17195,26 @@ int ldomNode::renderFinalBlock( LFormattedTextRef & frmtext, RenderRectAccessor
f = getDocument()->createFormattedText();
if ( rm != erm_final )
return 0;
- //RenderRectAccessor fmt( this );
- /// render whole node content as single formatted object
+
+ /// Render whole node content as single formatted object
+
+ // Get some properties cached in this node's RenderRectAccessor
+ // and set the initial flags and lang_cfg (for/from the final node
+ // itself) for renderFinalBlock(),
int direction = RENDER_RECT_PTR_GET_DIRECTION(fmt);
lUInt32 flags = styleToTextFmtFlags( true, getStyle(), 0, direction );
int lang_node_idx = fmt->getLangNodeIndex();
TextLangCfg * lang_cfg = TextLangMan::getTextLangCfg(lang_node_idx>0 ? getDocument()->getTinyNode(lang_node_idx) : NULL);
+
+ // Add this node's inner content (text and children nodes) as source text
+ // and image fragments into the empty LFormattedText object
::renderFinalBlock( this, f.get(), fmt, flags, 0, -1, lang_cfg );
// We need to store this LFormattedTextRef in the cache for it to
// survive when leaving this function (some callers do use it).
cache.set( this, f );
- bool flg=gFlgFloatingPunctuationEnabled;
- if (this->getNodeName()=="th"||this->getNodeName()=="td"||
- (!this->getParentNode()->isNull()&&this->getParentNode()->getNodeName()=="td")||
- (!this->getParentNode()->isNull()&&this->getParentNode()->getNodeName()=="th")) {
- gFlgFloatingPunctuationEnabled=false;
- }
+
+ // Gather some outer properties and context, so we can format (render)
+ // the inner content in that context.
// This page_h we provide to f->Format() is only used to enforce a max height to images
int page_h = getDocument()->getPageHeight();
// Save or restore outer floats footprint (it is only provided
@@ -17183,8 +17236,22 @@ int ldomNode::renderFinalBlock( LFormattedTextRef & frmtext, RenderRectAccessor
// one that is on a page to be drawn will be reformatted .
f->requestLightFormatting();
}
- int h = f->Format((lUInt16)width, (lUInt16)page_h, direction, float_footprint);
- gFlgFloatingPunctuationEnabled=flg;
+ int usable_left_overflow = fmt->getUsableLeftOverflow();
+ int usable_right_overflow = fmt->getUsableRightOverflow();
+
+ // Note: some properties are set into LFormattedText by lvrend.cpp's renderFinalBlock(),
+ // while some others are only passed below as parameters to LFormattedText->Format().
+ // The former should logically be source inner content properties (strut, text indent)
+ // while the latter should be formatting and outer context properties (block width,
+ // page height...).
+ // There might be a few drifts from that logic, or duplicates ('direction' is
+ // passed both ways), that could need a little rework.
+
+ // Format/render inner content: this makes lines and words, which are
+ // cached into the LFormattedText and ready to be used for drawing
+ // and text selection.
+ int h = f->Format((lUInt16)width, (lUInt16)page_h, direction, usable_left_overflow, usable_right_overflow,
+ gHangingPunctuationEnabled, float_footprint);
frmtext = f;
//CRLog::trace("Created new formatted object for node #%08X", (lUInt32)this);
return h;
diff --git a/crengine/src/textlang.cpp b/crengine/src/textlang.cpp
index c5bc9edc6..149e6a023 100644
--- a/crengine/src/textlang.cpp
+++ b/crengine/src/textlang.cpp
@@ -705,3 +705,183 @@ lString16 & TextLangCfg::getClosingQuote( bool update_level ) {
_quote_nesting_level--;
return ((_quote_nesting_level+1) % 2) ? _close_quote1 : _close_quote2;
}
+
+int TextLangCfg::getHyphenHangingPercent() {
+ return 70; // 70%
+}
+
+int TextLangCfg::getHangingPercent( bool right_hanging, bool & check_font, const lChar16 * text, int pos, int next_usable ) {
+ // We get provided with the BiDi re-ordered m_text (so, visually
+ // ordered) and the index of char: if needed, we can look at
+ // previous or next chars for context to decide how much to hang
+ // (i.e. consecutive punctuations).
+
+ // If we ever need to tweak this per language, try to avoid checks
+ // for the lang_tag in here:
+ // - either set bool members to enable or disable some checks and tweaks
+ // - or make this hanging_percent_func_generic, and add dedicated
+ // functions per language, hanging_percent_func_french, that
+ // could fallback to calling hanging_percent_func_generic after
+ // some checks - and have TextLangCfg::getHangingPercent() call
+ // the dedicated function pointer stored as a member.
+
+ // We might want to prevent any hanging with Chinese and Japanese
+ // as the text might be mostly full-width glyphs, and this might
+ // break the grid. This is less risky if the main font is a CJK
+ // font, but if it is not, punctuation might be picked from the
+ // main non-CJK font and won't be full-width.
+ // Or we could round any value to 0 or 100% (and/or tweak any
+ // glyph in lvtextfm.cpp so it looks like it is full-width).
+
+ lChar16 ch = text[pos];
+ int ratio = 0;
+
+ // In French, there's usually a space before and after guillemets,
+ // or before a quotation mark. Having them hanging, and then a
+ // space, looks like there's a hole in the margin.
+ // So, avoid hanging if the next/prev char is a space char.
+ // This might not happen in other languages, so let's do that
+ // prevention generically. If needed, make that dependant on
+ // a boolean member, set to true if LANG_STARTS_WITH(("fr")).
+ if ( right_hanging ) {
+ if ( pos > 0 ) {
+ lChar16 prev_ch = text[pos-1];
+ if ( prev_ch == 0x0020 || prev_ch == 0x00A0 || (prev_ch >= 0x2000 && prev_ch <= 0x200A ) ) {
+ // Normal space, no-break space, and other unicode spaces (except zero-width ones)
+ return 0;
+ }
+ }
+ }
+ else {
+ if ( next_usable > 0 ) {
+ lChar16 next_ch = text[pos+1];
+ if ( next_ch == 0x0020 || next_ch == 0x00A0 || (next_ch >= 0x2000 && next_ch <= 0x200A ) ) {
+ // Normal space, no-break space, and other unicode spaces (except zero-width ones)
+ return 0;
+ }
+ }
+ }
+
+ // For the common punctuations, parens and quotes, we check and
+ // return the same value whether asked for left or right hanging.
+ // Normally, libunibreak has prevented them from happening on
+ // one of the sides - but with RTL text, they may happen on
+ // the other side. Also, some BiDi mirrorable chars "([])" might
+ // be mirrored in the provided *text when not-using HarfBuzz, but
+ // won't be mirrored when using HarfBuzz - so let's handle
+ // all of them no matter the hanging side asked for.
+ // Also, because in some languages, quotation marks and guillemets
+ // are used reverted, we include left and right ones in both sets.
+
+ // Most values taken from the "protusion" section in:
+ // https://source.contextgarden.net/tex/context/base/mkiv/font-imp-quality.lua
+ // https://www.w3.org/Mail/flatten/index?subject=Amending+hanging-punctuation+for+Western+typography&list=www-style
+ // and the microtypography thesis: http://www.pragma-ade.nl/pdftex/thesis.pdf
+ // (screenshot at https://github.com/koreader/koreader/issues/6235#issuecomment-639307634)
+
+ switch (ch) {
+ case 0x0027: // ' single quote
+ case 0x002C: // , comma
+ case 0x002D: // - minus
+ case 0x002E: // . period
+ case 0x0060: // ` back quote
+ // case 0x00AD: // soft hyphen (we don't draw them, so don't handle them)
+ case 0x060C: // ، arabic comma
+ case 0x06D4: // ۔ arabic full stop
+ case 0x2010: // ‐ hyphen
+ case 0x2018: // ‘ left single quotation mark
+ case 0x2019: // ’ right single quotation mark
+ case 0x201A: // ‚ single low-9 quotation mark
+ case 0x201B: // ‛ single high-reversed-9 quotation mark
+ case 0x2039: // ‹ left single guillemet
+ case 0x203A: // › right single guillemet
+ ratio = 70;
+ break;
+ case 0x0022: // " double quote
+ case 0x003A: // : colon
+ case 0x003B: // ; semicolon
+ case 0x00AB: // « left guillemet
+ case 0x00BB: // » right guillemet
+ case 0x061B: // ؛ arabic semicolon
+ case 0x201C: // “ left double quotation mark
+ case 0x201D: // ” right double quotation mark
+ case 0x201E: // „ double low-9 quotation mark
+ case 0x201F: // ‟ double high-reversed-9 quotation mark
+ ratio = 50;
+ break;
+ case 0x2013: // – endash
+ ratio = 30;
+ break;
+ case 0x0021: // !
+ case 0x003F: // ?
+ case 0x00A1: // ¡
+ case 0x00BF: // ¿
+ case 0x061F: // ؟
+ case 0x2014: // — emdash
+ case 0x2026: // … ellipsis
+ ratio = 20;
+ break;
+ case 0x0028: // (
+ case 0x0029: // )
+ case 0x005B: // [
+ case 0x005D: // ]
+ case 0x007B: // {
+ case 0x007D: // }
+ ratio = 5;
+ break;
+ default:
+ break;
+ }
+ if ( ratio ) {
+ check_font = false;
+ return ratio;
+ }
+ // Other are non punctuation but slight adjustment for some letters,
+ // that might be ignored if the font already include some negative
+ // left side bearing.
+ check_font = true;
+ if ( right_hanging ) {
+ switch (ch) {
+ case 'A':
+ case 'F':
+ case 'K':
+ case 'L':
+ case 'T':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'k':
+ case 'r':
+ case 't':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ ratio = 5;
+ break;
+ default:
+ break;
+ }
+ }
+ else { // left hanging
+ switch (ch) {
+ case 'A':
+ case 'J':
+ case 'T':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ ratio = 5;
+ break;
+ default:
+ break;
+ }
+ }
+ return ratio;
+}