=== modified file 'src/2geom/generic-rect.h' --- src/2geom/generic-rect.h 2012-02-19 19:53:29 +0000 +++ src/2geom/generic-rect.h 2013-05-15 19:48:25 +0000 @@ -261,7 +261,7 @@ expandBy(amount, amount); } /** @brief Expand the rectangle in both directions. - * Note that this is different from scaling. Negative values wil shrink the + * Note that this is different from scaling. Negative values will shrink the * rectangle. If -x is larger than * half of the width, the X interval will contain only the X coordinate * of the midpoint; same for height. */ === modified file 'src/attributes-test.h' --- src/attributes-test.h 2012-11-30 19:22:44 +0000 +++ src/attributes-test.h 2013-04-30 15:52:59 +0000 @@ -276,6 +276,9 @@ {"text-align", true}, {"text-anchor", true}, {"text-decoration", true}, + {"text-decoration-line", false}, + {"text-decoration-style", false}, + {"text-decoration-color", false}, {"text-indent", true}, {"text-rendering", true}, {"text-transform", true}, === modified file 'src/attributes.cpp' --- src/attributes.cpp 2012-12-22 16:34:31 +0000 +++ src/attributes.cpp 2013-04-30 15:52:59 +0000 @@ -414,6 +414,9 @@ {SP_PROP_TEXT_INDENT, "text-indent"}, {SP_PROP_TEXT_ALIGN, "text-align"}, {SP_PROP_TEXT_DECORATION, "text-decoration"}, + {SP_PROP_TEXT_DECORATION_LINE, "text-decoration-line"}, + {SP_PROP_TEXT_DECORATION_STYLE,"text-decoration-style"}, + {SP_PROP_TEXT_DECORATION_COLOR,"text-decoration-color"}, {SP_PROP_LINE_HEIGHT, "line-height"}, {SP_PROP_LETTER_SPACING, "letter-spacing"}, {SP_PROP_WORD_SPACING, "word-spacing"}, === modified file 'src/attributes.h' --- src/attributes.h 2012-12-22 16:34:31 +0000 +++ src/attributes.h 2013-05-10 18:10:39 +0000 @@ -414,7 +414,10 @@ /* Text */ SP_PROP_TEXT_INDENT, SP_PROP_TEXT_ALIGN, - SP_PROP_TEXT_DECORATION, + SP_PROP_TEXT_DECORATION, /* SVG 1 underline etc.( no color or style) OR SVG2 with _LINE, _STYLE, _COLOR values */ + SP_PROP_TEXT_DECORATION_LINE, /* SVG 2 underline etc. */ + SP_PROP_TEXT_DECORATION_STYLE, /* SVG 2 proposed solid [SVG 1], dotted, etc.)*/ + SP_PROP_TEXT_DECORATION_COLOR, /* SVG 2 proposed same as text [SVG 1], specified*/ SP_PROP_LINE_HEIGHT, SP_PROP_LETTER_SPACING, SP_PROP_WORD_SPACING, === modified file 'src/display/drawing-context.h' --- src/display/drawing-context.h 2012-11-29 09:15:34 +0000 +++ src/display/drawing-context.h 2013-05-20 18:45:39 +0000 @@ -67,6 +67,21 @@ void rectangle(Geom::IntRect const &r) { cairo_rectangle(_ct, r.left(), r.top(), r.width(), r.height()); } + // Used in drawing-text.cpp to overwrite glyphs, which have the opposite path rotation as a regular rect + void revrectangle(Geom::Rect const &r) { + cairo_move_to ( _ct, r.left(), r.top() ); + cairo_rel_line_to (_ct, 0, r.height() ); + cairo_rel_line_to (_ct, r.width(), 0 ); + cairo_rel_line_to (_ct, 0, -r.height() ); + cairo_close_path ( _ct); + } + void revrectangle(Geom::IntRect const &r) { + cairo_move_to ( _ct, r.left(), r.top() ); + cairo_rel_line_to (_ct, 0, r.height() ); + cairo_rel_line_to (_ct, r.width(), 0 ); + cairo_rel_line_to (_ct, 0, -r.height() ); + cairo_close_path ( _ct); + } void newPath() { cairo_new_path(_ct); } void newSubpath() { cairo_new_sub_path(_ct); } void path(Geom::PathVector const &pv); === modified file 'src/display/drawing-text.cpp' --- src/display/drawing-text.cpp 2012-11-06 18:36:17 +0000 +++ src/display/drawing-text.cpp 2013-05-20 21:46:41 +0000 @@ -18,9 +18,11 @@ #include "helper/geom.h" #include "libnrtype/font-instance.h" #include "style.h" +#include "2geom/pathvector.h" namespace Inkscape { + DrawingGlyphs::DrawingGlyphs(Drawing &drawing) : DrawingItem(drawing) , _font(NULL) @@ -61,12 +63,34 @@ return STATE_ALL; } + _pick_bbox = Geom::IntRect(); _bbox = Geom::IntRect(); - Geom::OptRect b = bounds_exact_transformed(*_font->PathVector(_glyph), ctx.ctm); + Geom::OptRect b; + +/* orignally it did the one line below, + but it did not handle ws characters at all, and it had problems with scaling for overline/underline. + Replaced with the section below, which seems to be much more stable. + + b = bounds_exact_transformed(*_font->PathVector(_glyph), ctx.ctm); +*/ + /* Make a bounding box that is a little taller and lower (currently 10% extra) than the font's drawing box. Extra space is + to hold overline or underline, if present. All characters in a font use the same ascent and descent, + but different widths. This lets leading and trailing spaces have text decorations. If it is not done + the bounding box is limited to the box surrounding the drawn parts of visible glyphs only, and draws outside are ignored. + */ + + float scale = 1.0; + if(_transform){ scale /= _transform->descrim(); } + + Geom::Rect bigbox(Geom::Point(0.0, _asc*scale*1.1),Geom::Point(_width*scale, -_dsc*scale*1.1)); + b = bigbox * ctx.ctm; + if (b && (ggroup->_nrstyle.stroke.type != NRStyle::PAINT_NONE)) { float width, scale; + + // this expands the selection box for cases where the stroke is "thick" scale = ctx.ctm.descrim(); if (_transform) { scale /= _transform->descrim(); // FIXME temporary hack @@ -75,7 +99,8 @@ if ( fabs(ggroup->_nrstyle.stroke_width * scale) > 0.01 ) { // FIXME: this is always true b->expandBy(0.5 * width); } - // save bbox without miters for picking + + // save bbox without miters for picking _pick_bbox = b->roundOutwards(); float miterMax = width * ggroup->_nrstyle.miter_limit; @@ -89,7 +114,12 @@ _bbox = b->roundOutwards(); _pick_bbox = *_bbox; } - +/* +std::cout << "DEBUG _bbox" +<< " { " << _bbox->min()[Geom::X] << " , " << _bbox->min()[Geom::Y] +<< " } , { " << _bbox->max()[Geom::X] << " , " << _bbox->max()[Geom::Y] +<< " }" << std::endl; +*/ return STATE_ALL; } @@ -106,7 +136,7 @@ return NULL; } - // With text we take a simple approach: pick if the point is in a characher bbox + // With text we take a simple approach: pick if the point is in a character bbox Geom::Rect expanded(_pick_bbox); expanded.expandBy(delta); if (expanded.contains(p)) return this; @@ -129,17 +159,28 @@ _children.clear_and_dispose(DeleteDisposer()); } -void -DrawingText::addComponent(font_instance *font, int glyph, Geom::Affine const &trans) +bool +DrawingText::addComponent(font_instance *font, int glyph, Geom::Affine const &trans, + float width, float ascent, float descent, float phase_length) { +/* original, did not save a glyph for white space characters, causes problems for text-decoration if (!font || !font->PathVector(glyph)) { - return; + return(false); } +*/ + if (!font)return(false); _markForRendering(); DrawingGlyphs *ng = new DrawingGlyphs(_drawing); ng->setGlyph(font, glyph, trans); + if(font->PathVector(glyph)){ ng->_drawable = true; } + else { ng->_drawable = false; } + ng->_width = width; // only used when _drawable = false + ng->_asc = ascent; // of font, not of this one character + ng->_dsc = descent; // of font, not of this one character + ng->_pl = phase_length; // used for phase of dots, dashes, and wavy appendChild(ng); + return(true); } void @@ -156,9 +197,175 @@ return DrawingGroup::_updateItem(area, ctx, flags, reset); } +void DrawingText::decorateStyle(DrawingContext &ct, double vextent, double xphase, Geom::Point p1, Geom::Point p2) +{ + double wave[16]={ + 0.000000, 0.382499, 0.706825, 0.923651, 1.000000, 0.923651, 0.706825, 0.382499, + 0.000000, -0.382499, -0.706825, -0.923651, -1.000000, -0.923651, -0.706825, -0.382499, + }; + int dashes[16]={ + 8, 7, 6, 5, + 4, 3, 2, 1, + -8, -7, -6, -5 + -4, -3, -2, -1 + }; + int dots[16]={ + 4, 3, 2, 1, + -4, -3, -2, -1, + 4, 3, 2, 1, + -4, -3, -2, -1 + }; + Geom::Point p3,p4,ps,pf; + double step = vextent/32.0; + unsigned i = 15 & (unsigned) round(xphase/step); // xphase is >= 0.0 + + /* For most spans draw the last little bit right to p2 or even a little beyond. + This allows decoration continuity within the line, and does not step outside the clip box off the end + For the first/last section on the line though, stay well clear of the edge, or when the + text is dragged it may "spray" pixels. + if(_nrstyle.tspan_line_end){ pf = p2 - Geom::Point(2*step, 0.0); } + else { pf = p2; } + if(_nrstyle.tspan_line_start){ ps = p1 + Geom::Point(2*step, 0.0); + i = 15 & (i + 2); + } + else { ps = p1; } + */ + /* snap to nearest step in X */ +ps = Geom::Point(step * round(p1[Geom::X]/step),p1[Geom::Y]); +pf = Geom::Point(step * round(p2[Geom::X]/step),p2[Geom::Y]); + + if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_ISDOUBLE){ + ps -= Geom::Point(0, vextent/12.0); + pf -= Geom::Point(0, vextent/12.0); + ct.moveTo(ps); + ct.lineTo(pf); + ps += Geom::Point(0, vextent/6.0); + pf += Geom::Point(0, vextent/6.0); + ct.moveTo(ps); + ct.lineTo(pf); + } + /* The next three have a problem in that they are phase dependent. The bits of a line are not + necessarily passing through this routine in order, so we have to use the xphase information + to figure where in each of their cycles to start. Only accurate to 1 part in 16. + Huge possitive offset should keep the phase calculation from ever being negative. + */ + else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_DOTTED){ + while(1){ + if(dots[i]>0){ + if(ps[Geom::X]> pf[Geom::X])break; + ct.moveTo(ps); + ps += Geom::Point(step * (double)dots[i], 0.0); + if(ps[Geom::X]>= pf[Geom::X]){ + ct.lineTo(pf); + break; + } + else { + ct.lineTo(ps); + } + ps += Geom::Point(step * 4.0, 0.0); + } + else { + ps += Geom::Point(step * -(double)dots[i], 0.0); + } + i = 0; // once in phase, it stays in phase + } + } + else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_DASHED){ + while(1){ + if(dashes[i]>0){ + if(ps[Geom::X]> pf[Geom::X])break; + ct.moveTo(ps); + ps += Geom::Point(step * (double)dashes[i], 0.0); + if(ps[Geom::X]>= pf[Geom::X]){ + ct.lineTo(pf); + break; + } + else { + ct.lineTo(ps); + } + ps += Geom::Point(step * 8.0, 0.0); + } + else { + ps += Geom::Point(step * -(double)dashes[i], 0.0); + } + i = 0; // once in phase, it stays in phase + } + } + else if(_nrstyle.text_decoration_style & TEXT_DECORATION_STYLE_WAVY){ + double amp = vextent/10.0; + double x = ps[Geom::X]; + double y = ps[Geom::Y]; + ct.moveTo(Geom::Point(x, y + amp * wave[i])); + while(1){ + i = ((i + 1) & 15); + x += step; + ct.lineTo(Geom::Point(x, y + amp * wave[i])); + if(x >= pf[Geom::X])break; + } + } + else { // TEXT_DECORATION_STYLE_SOLID, also default in case it was not set for some reason + ct.moveTo(ps); + ct.lineTo(pf); +// ct.revrectangle(Geom::Rect(ps,pf)); + } +} + +/* returns scaled line thickness */ +double DrawingText::decorateItem(DrawingContext &ct, Geom::Affine aff, double phase_length) +{ + double tsp_width_adj, tsp_asc_adj, tsp_size_adj; + double final_underline_thickness, final_line_through_thickness; + double thickness; + + tsp_width_adj = _nrstyle.tspan_width / _nrstyle.font_size; + tsp_asc_adj = _nrstyle.ascender / _nrstyle.font_size; + tsp_size_adj = (_nrstyle.ascender + _nrstyle.descender) / _nrstyle.font_size; +#define VALTRUNC(A,B,C) (A < B ? B : ( A > C ? C : A )) + final_underline_thickness = VALTRUNC(_nrstyle.underline_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0); + final_line_through_thickness = VALTRUNC(_nrstyle.line_through_thickness, tsp_size_adj/30.0, tsp_size_adj/10.0); + Inkscape::DrawingContext::Save save(ct); + + double scale = aff.descrim(); + double xphase = phase_length/ _nrstyle.font_size; // used to figure out phase of patterns + + ct.transform(aff); // must be leftmost affine in span + Geom::Point p1; + Geom::Point p2; + // All lines must be the same thickness, in combinations, line_through trumps underline + thickness = final_underline_thickness; + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_UNDERLINE){ + p1 = Geom::Point(0.0, -_nrstyle.underline_position); + p2 = Geom::Point(tsp_width_adj,-_nrstyle.underline_position); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2); + } + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_OVERLINE){ + p1 = Geom::Point(0.0, tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); + p2 = Geom::Point(tsp_width_adj,tsp_asc_adj -_nrstyle.underline_position + 1 * final_underline_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2); + } + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_LINETHROUGH){ + thickness = final_line_through_thickness; + p1 = Geom::Point(0.0, _nrstyle.line_through_position); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2); + } + // Obviously this does not blink, but it does indicate which text has been set with that attribute + if(_nrstyle.text_decoration_line & TEXT_DECORATION_LINE_BLINK){ + thickness = final_line_through_thickness; + p1 = Geom::Point(0.0, _nrstyle.line_through_position - 2*final_line_through_thickness); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position - 2*final_line_through_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2); + p1 = Geom::Point(0.0, _nrstyle.line_through_position + 2*final_line_through_thickness); + p2 = Geom::Point(tsp_width_adj,_nrstyle.line_through_position + 2*final_line_through_thickness); + decorateStyle(ct, tsp_size_adj, xphase, p1, p2); + } + thickness *= scale; + return(thickness); +} + unsigned DrawingText::_renderItem(DrawingContext &ct, Geom::IntRect const &/*area*/, unsigned /*flags*/, DrawingItem * /*stop_at*/) { - if (_drawing.outline()) { + if (_drawing.outline()) { guint32 rgba = _drawing.outlinecolor; Inkscape::DrawingContext::Save save(ct); ct.setSource(rgba); @@ -169,34 +376,71 @@ if (!g) throw InvalidItemException(); Inkscape::DrawingContext::Save save(ct); - // skip glpyhs with singular transforms + // skip glyphs with singular transforms if (g->_ctm.isSingular()) continue; ct.transform(g->_ctm); - ct.path(*g->_font->PathVector(g->_glyph)); - ct.fill(); + if(g->_drawable){ + ct.path(*g->_font->PathVector(g->_glyph)); + ct.fill(); + } } return RENDER_OK; } // NOTE: this is very similar to drawing-shape.cpp; the only difference is in path feeding bool has_stroke, has_fill; - - has_fill = _nrstyle.prepareFill(ct, _item_bbox); - has_stroke = _nrstyle.prepareStroke(ct, _item_bbox); + double leftmost = DBL_MAX; + double phase_length = 0.0; + Geom::Affine aff; + using Geom::X; + + Geom::OptRect pbox = _item_bbox; + if(_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR){ + // no easy way of determing how the bounding box is lined up vertically and horizontally with respect + // to the text inside it, so add a little extra space. Horizontally it should be close to + // centered so less wiggle room is required. Some languages are really "tall", like Hebrew, and they + // need the extra headroom or overline does not work. + // Horizontal measure should clear the +1/16th width overrun of wave, dot, or dash + pbox->expandBy( + (_nrstyle.ascender + _nrstyle.descender)/2.0, + 1.25*((_nrstyle.ascender + _nrstyle.descender) - (pbox->bottom() - pbox->top())) ); + pbox = pbox->roundOutwards(); + } + + has_fill = _nrstyle.prepareFill( ct, pbox); + has_stroke = _nrstyle.prepareStroke(ct, pbox); if (has_fill || has_stroke) { + Geom::Affine rotinv; + bool invset=false; for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { DrawingGlyphs *g = dynamic_cast(&*i); if (!g) throw InvalidItemException(); + if(!invset){ + rotinv = g->_ctm.withoutTranslation().inverse(); + invset = true; + } Inkscape::DrawingContext::Save save(ct); if (g->_ctm.isSingular()) continue; ct.transform(g->_ctm); - ct.path(*g->_font->PathVector(g->_glyph)); + if(g->_drawable){ + ct.path(*g->_font->PathVector(g->_glyph)); + } + // get the leftmost affine transform (leftmost defined with respect to the x axis of the first transform). + // That way the decoration will work no matter what mix of L->R, R->L text is in the span. + if(_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR){ + Geom::Point pt =g->_ctm.translation() * rotinv; + if(pt[X] < leftmost){ + leftmost = pt[X]; + aff = g->_ctm; + phase_length = g->_pl; + } + } } Inkscape::DrawingContext::Save save(ct); - ct.transform(_ctm); +// ct.transform(_ctm); // Seems to work fine without this line, which was in the original. if (has_fill) { _nrstyle.applyFill(ct); ct.fillPreserve(); @@ -206,6 +450,29 @@ ct.strokePreserve(); } ct.newPath(); // clear path + if(_nrstyle.text_decoration_line != TEXT_DECORATION_LINE_CLEAR){ + guint32 ergba; + if(_nrstyle.text_decoration_useColor){ // color different from the glyph + ergba = SP_RGBA32_F_COMPOSE( + _nrstyle.text_decoration_color.color.v.c[0], + _nrstyle.text_decoration_color.color.v.c[1], + _nrstyle.text_decoration_color.color.v.c[2], + 1.0); + } + else { // whatever the current fill color is + ergba = SP_RGBA32_F_COMPOSE( + _nrstyle.fill.color.v.c[0], + _nrstyle.fill.color.v.c[1], + _nrstyle.fill.color.v.c[2], + 1.0); + } + ct.setSource(ergba); + ct.setTolerance(0.5); + double thickness = decorateItem(ct, aff, phase_length); + ct.setLineWidth(thickness); + ct.strokePreserve(); + ct.newPath(); // clear path + } } return RENDER_OK; } @@ -231,7 +498,9 @@ Inkscape::DrawingContext::Save save(ct); ct.transform(g->_ctm); - ct.path(*g->_font->PathVector(g->_glyph)); + if(g->_drawable){ + ct.path(*g->_font->PathVector(g->_glyph)); + } } ct.fill(); } === modified file 'src/display/drawing-text.h' --- src/display/drawing-text.h 2013-03-14 10:28:27 +0000 +++ src/display/drawing-text.h 2013-05-20 20:00:58 +0000 @@ -35,8 +35,13 @@ virtual DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags); font_instance *_font; - int _glyph; - Geom::IntRect _pick_bbox; + int _glyph; + bool _drawable; + float _width; // These three are used to set up bounding box + float _asc; // + float _dsc; // + float _pl; // phase length + Geom::IntRect _pick_bbox; friend class DrawingText; }; @@ -49,9 +54,11 @@ ~DrawingText(); void clear(); - void addComponent(font_instance *font, int glyph, Geom::Affine const &trans); + bool addComponent(font_instance *font, int glyph, Geom::Affine const &trans, + float width, float ascent, float descent, float phase_length); void setStyle(SPStyle *style); + protected: virtual unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset); @@ -61,6 +68,8 @@ virtual DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags); virtual bool _canClip(); + double decorateItem(DrawingContext &ct, Geom::Affine aff, double phase_length); + void decorateStyle(DrawingContext &ct, double vextent, double xphase, Geom::Point p1, Geom::Point p2); NRStyle _nrstyle; friend class DrawingGlyphs; === modified file 'src/display/nr-style.cpp' --- src/display/nr-style.cpp 2013-04-11 09:58:39 +0000 +++ src/display/nr-style.cpp 2013-05-20 19:56:58 +0000 @@ -54,6 +54,20 @@ , line_join(CAIRO_LINE_JOIN_MITER) , fill_pattern(NULL) , stroke_pattern(NULL) + , text_decoration_line(TEXT_DECORATION_LINE_CLEAR) + , text_decoration_style(TEXT_DECORATION_STYLE_CLEAR) + , phase_length(0.0) + , tspan_line_start(false) + , tspan_line_end(false) + , tspan_width(0) + , ascender(0) + , descender(0) + , line_gap(0) + , underline_thickness(0) + , underline_position(0) + , line_through_thickness(0) + , line_through_position(0) + , font_size(0) {} NRStyle::~NRStyle() @@ -144,6 +158,50 @@ dash = NULL; } + text_decoration_line = TEXT_DECORATION_LINE_CLEAR; + if(style->text_decoration_line.inherit ){ text_decoration_line |= TEXT_DECORATION_LINE_INHERIT; } + if(style->text_decoration_line.underline ){ text_decoration_line |= TEXT_DECORATION_LINE_UNDERLINE + TEXT_DECORATION_LINE_SET; } + if(style->text_decoration_line.overline ){ text_decoration_line |= TEXT_DECORATION_LINE_OVERLINE + TEXT_DECORATION_LINE_SET; } + if(style->text_decoration_line.line_through){ text_decoration_line |= TEXT_DECORATION_LINE_LINETHROUGH + TEXT_DECORATION_LINE_SET; } + if(style->text_decoration_line.blink ){ text_decoration_line |= TEXT_DECORATION_LINE_BLINK + TEXT_DECORATION_LINE_SET; } + + text_decoration_style = TEXT_DECORATION_STYLE_CLEAR; + if(style->text_decoration_style.inherit ){ text_decoration_style |= TEXT_DECORATION_STYLE_INHERIT; } + if(style->text_decoration_style.solid ){ text_decoration_style |= TEXT_DECORATION_STYLE_SOLID + TEXT_DECORATION_STYLE_SET; } + if(style->text_decoration_style.isdouble ){ text_decoration_style |= TEXT_DECORATION_STYLE_ISDOUBLE + TEXT_DECORATION_STYLE_SET; } + if(style->text_decoration_style.dotted ){ text_decoration_style |= TEXT_DECORATION_STYLE_DOTTED + TEXT_DECORATION_STYLE_SET; } + if(style->text_decoration_style.dashed ){ text_decoration_style |= TEXT_DECORATION_STYLE_DASHED + TEXT_DECORATION_STYLE_SET; } + if(style->text_decoration_style.wavy ){ text_decoration_style |= TEXT_DECORATION_STYLE_WAVY + TEXT_DECORATION_STYLE_SET; } + + if( style->text_decoration_color.set || + style->text_decoration_color.inherit || + style->text_decoration_color.currentcolor || + style->text_decoration_color.colorSet){ + text_decoration_color.set(style->text_decoration_color.value.color); + text_decoration_useColor = true; + } + else { + text_decoration_color.clear(); + text_decoration_useColor = false; + } + + if(text_decoration_line != TEXT_DECORATION_LINE_CLEAR){ + phase_length = style->text_decoration_data.phase_length; + tspan_line_start = style->text_decoration_data.tspan_line_start; + tspan_line_end = style->text_decoration_data.tspan_line_end; + tspan_width = style->text_decoration_data.tspan_width; + ascender = style->text_decoration_data.ascender; + descender = style->text_decoration_data.descender; + line_gap = style->text_decoration_data.line_gap; + underline_thickness = style->text_decoration_data.underline_thickness; + underline_position = style->text_decoration_data.underline_position; + line_through_thickness = style->text_decoration_data.line_through_thickness; + line_through_position = style->text_decoration_data.line_through_position; + font_size = style->font_size.computed; + } + + text_direction = style->direction.computed; + update(); } === modified file 'src/display/nr-style.h' --- src/display/nr-style.h 2013-04-11 09:58:39 +0000 +++ src/display/nr-style.h 2013-05-20 19:48:51 +0000 @@ -67,6 +67,43 @@ cairo_pattern_t *fill_pattern; cairo_pattern_t *stroke_pattern; + +#define TEXT_DECORATION_LINE_CLEAR 0x00 +#define TEXT_DECORATION_LINE_SET 0x01 +#define TEXT_DECORATION_LINE_INHERIT 0x02 +#define TEXT_DECORATION_LINE_UNDERLINE 0x04 +#define TEXT_DECORATION_LINE_OVERLINE 0x08 +#define TEXT_DECORATION_LINE_LINETHROUGH 0x10 +#define TEXT_DECORATION_LINE_BLINK 0x20 + +#define TEXT_DECORATION_STYLE_CLEAR 0x00 +#define TEXT_DECORATION_STYLE_SET 0x01 +#define TEXT_DECORATION_STYLE_INHERIT 0x02 +#define TEXT_DECORATION_STYLE_SOLID 0x04 +#define TEXT_DECORATION_STYLE_ISDOUBLE 0x08 +#define TEXT_DECORATION_STYLE_DOTTED 0x10 +#define TEXT_DECORATION_STYLE_DASHED 0x20 +#define TEXT_DECORATION_STYLE_WAVY 0x40 + + int text_decoration_line; + int text_decoration_style; + Paint text_decoration_color; + bool text_decoration_useColor; // if false, use whatever the glyph color was + // These are the same as in style.h + float phase_length; + bool tspan_line_start; + bool tspan_line_end; + float tspan_width; + float ascender; + float descender; + float line_gap; + float underline_thickness; + float underline_position; + float line_through_thickness; + float line_through_position; + float font_size; + + int text_direction; }; #endif === modified file 'src/extension/internal/emf-inout.cpp' --- src/extension/internal/emf-inout.cpp 2013-03-21 14:07:06 +0000 +++ src/extension/internal/emf-inout.cpp 2013-05-14 18:22:08 +0000 @@ -1400,8 +1400,10 @@ pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER : U_FW_NORMAL; d->dc[d->level].style.font_style.value = (pEmr->elfw.elfLogFont.lfItalic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL); - d->dc[d->level].style.text_decoration.underline = pEmr->elfw.elfLogFont.lfUnderline; - d->dc[d->level].style.text_decoration.line_through = pEmr->elfw.elfLogFont.lfStrikeOut; + d->dc[d->level].style.text_decoration_line.underline = pEmr->elfw.elfLogFont.lfUnderline; + d->dc[d->level].style.text_decoration_line.line_through = pEmr->elfw.elfLogFont.lfStrikeOut; + d->dc[d->level].style.text_decoration_line.set = true; + d->dc[d->level].style.text_decoration_line.inherit = false; // malformed EMF with empty filename may exist, ignore font change if encountered char *ctmp = U_Utf16leToUtf8((uint16_t *) (pEmr->elfw.elfLogFont.lfFaceName), U_LF_FACESIZE, NULL); if(ctmp){ @@ -2988,6 +2990,7 @@ y1 = pEmr->emrtext.ptlReference.y; cChars = 0; } + uint32_t fOptions = pEmr->emrtext.fOptions; if (d->dc[d->level].textAlign & U_TA_UPDATECP) { x1 = d->dc[d->level].cur.x; @@ -3078,6 +3081,10 @@ case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; default: tsp.weight = FC_WEIGHT_NORMAL ; break; } + // EMF only supports two types of text decoration + tsp.decoration = TXTDECOR_NONE; + if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; } + if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;} // EMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER : @@ -3086,13 +3093,19 @@ tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE : ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT : ALITOP)); - tsp.ldir = (d->dc[d->level].textAlign & U_TA_RTLREADING ? LDIR_RL : LDIR_LR); // language direction + + // language direction can be encoded two ways, U_TA_RTLREADING is preferred + if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; } + else{ tsp.ldir = LDIR_LR; } + tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet) tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement tsp.ori += 180.0 * current_rotation(d)/ M_PI; // radians to degrees tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear(). tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points - (void) trinfo_load_fontname(d->tri, (uint8_t *)d->dc[d->level].font_name, &tsp); + char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name); + tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec); + free(fontspec); // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though // as the metrics from fontconfig may not match, or the font may not be present. if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; } @@ -3101,7 +3114,7 @@ int status = trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ori is actually escapement if(status==-1){ // change of escapement, emit what we have and reset TR_layout_analyze(d->tri); - TR_layout_2_svg(d->tri); + TR_layout_2_svg(d->tri); ts << d->tri->out; *(d->outsvg) += ts.str().c_str(); d->tri = trinfo_clear(d->tri); === modified file 'src/extension/internal/emf-print.cpp' --- src/extension/internal/emf-print.cpp 2013-03-21 14:07:06 +0000 +++ src/extension/internal/emf-print.cpp 2013-05-07 21:13:40 +0000 @@ -212,7 +212,7 @@ search_long_fflist(fontname, f1, f2, f3); } -void PrintEmf::smuggle_adxky_out(const char *string, uint32_t **adx, double *ky, int *ndx, float scale){ +void PrintEmf::smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale){ float fdx; int i; uint32_t *ladx; @@ -233,6 +233,8 @@ cptr++; // skip 2nd fake terminator sscanf(cptr,"%7f",&fdx); *ky=fdx; + cptr += 7; // advance over ky and its space + sscanf(cptr,"%07d",rtl); } /* convert an 0RGB color to EMF U_COLORREF. @@ -313,6 +315,7 @@ // initialize a few global variables hbrush = hbrushOld = hpen = 0; + htextalignment = U_TA_BASELINE | U_TA_LEFT; use_stroke = use_fill = simple_shape = usebk = false; Inkscape::XML::Node *nv = sp_repr_lookup_name (doc->rroot, "sodipodi:namedview"); @@ -418,33 +421,34 @@ rec = U_EMRSETBKMODE_set(U_TRANSPARENT); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ - g_error("Fatal programming error in PrintEmf::text at U_EMRSETBKMODE_set"); + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETBKMODE_set"); } hpolyfillmode=U_WINDING; rec = U_EMRSETPOLYFILLMODE_set(U_WINDING); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ - g_error("Fatal programming error in PrintEmf::text at U_EMRSETPOLYFILLMODE_set"); + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETPOLYFILLMODE_set"); } - // Text alignment: (Never changes) + // Text alignment: (only changed if RTL text is encountered ) // - (x,y) coordinates received by this filter are those of the point where the text // actually starts, and already takes into account the text object's alignment; // - for this reason, the EMF text alignment must always be TA_BASELINE|TA_LEFT. + htextalignment = U_TA_BASELINE | U_TA_LEFT; rec = U_EMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ - g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTALIGN_set"); + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTALIGN_set"); } htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; rec = U_EMRSETTEXTCOLOR_set(U_RGB(0,0,0)); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ - g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTCOLOR_set"); + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTCOLOR_set"); } rec = U_EMRSETROP2_set(U_R2_COPYPEN); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ - g_error("Fatal programming error in PrintEmf::text at U_EMRSETROP2_set"); + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETROP2_set"); } /* miterlimit is set with eah pen, so no need to check for it changes as in WMF */ @@ -1415,8 +1419,8 @@ } } if ( - (style->stroke.noneSet || style->stroke_width.computed == 0.0) || - (style->stroke_dash.n_dash && style->stroke_dash.dash && FixPPTDashLine) || + (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) || + (style->stroke_dash.n_dash && style->stroke_dash.dash && FixPPTDashLine) || !all_closed ){ print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv @@ -1977,10 +1981,21 @@ double ky; // the dx array is smuggled in like: textw1 w2 w3 ...wn, where the widths are floats 7 characters wide, including the space - int ndx; + int ndx, rtl; uint32_t *adx; - smuggle_adxky_out(text, &adx, &ky, &ndx, PX2WORLD * std::min(tf.expansionX(),tf.expansionY())); // side effect: free() adx + smuggle_adxkyrtl_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(),tf.expansionY())); // side effect: free() adx + uint32_t textalignment; + if(rtl > 0){ textalignment = U_TA_BASELINE | U_TA_LEFT; } + else { textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING; } + if(textalignment != htextalignment){ + htextalignment = textalignment; + rec = U_EMRSETTEXTALIGN_set(textalignment); + if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ + g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTALIGN_set"); + } + } + char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char * uint16_t *unicode_text = U_Utf8ToUtf16le( text2, 0, NULL ); free(text2); @@ -2043,8 +2058,8 @@ round(rot), transweight(style->font_weight.computed), (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC), - style->text_decoration.underline, - style->text_decoration.line_through, + style->text_decoration_line.underline, + style->text_decoration_line.line_through, U_DEFAULT_CHARSET, U_OUT_DEFAULT_PRECIS, U_CLIP_DEFAULT_PRECIS, @@ -2109,6 +2124,7 @@ int32_t const xpos = (int32_t) round(p2[Geom::X]); int32_t const ypos = (int32_t) round(p2[Geom::Y]); + // The number of characters in the string is a bit fuzzy. ndx, the number of entries in adx is // the number of VISIBLE characters, since some may combine from the UTF (8 originally, // now 16) encoding. Conversely strlen() or wchar16len() would give the absolute number of @@ -2117,7 +2133,13 @@ // This is currently being smuggled in from caller as part of text, works // MUCH better than the fallback hack below // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up - char *rec2 = emrtext_set( (U_POINTL) {xpos, ypos}, ndx, 2, unicode_text, U_ETO_NONE, U_RCL_DEF, adx); + char *rec2; + if(rtl>0){ + rec2 = emrtext_set( (U_POINTL) {xpos, ypos}, ndx, 2, unicode_text, U_ETO_NONE, U_RCL_DEF, adx); + } + else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case + rec2 = emrtext_set( (U_POINTL) {xpos, ypos}, ndx, 2, unicode_text, U_ETO_RTLREADING, U_RCL_DEF, adx); + } free(unicode_text); free(adx); rec = U_EMREXTTEXTOUTW_set(U_RCL_DEF,U_GM_COMPATIBLE,1.0,1.0,(PU_EMRTEXT)rec2); === modified file 'src/extension/internal/emf-print.h' --- src/extension/internal/emf-print.h 2013-03-19 02:22:04 +0000 +++ src/extension/internal/emf-print.h 2013-04-30 15:52:59 +0000 @@ -38,6 +38,7 @@ double _height; U_RECTL rc; + uint32_t htextalignment; uint32_t hbrush, hbrushOld, hpen, hpenOld; uint32_t hpolyfillmode; // used to minimize redundant records that set this float htextcolor_rgb[3]; // used to minimize redundant records that set this @@ -93,7 +94,7 @@ static void read_system_fflist(void); static void search_long_fflist(const char *fontname, double *f1, double *f2, double *f3); static void search_short_fflist(const char *fontname, double *f1, double *f2, double *f3); - static void smuggle_adxky_out(const char *string, uint32_t **adx, double *ky, int *ndx, float scale); + static void smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale); U_COLORREF gethexcolor(uint32_t color); uint32_t transweight(const unsigned int inkweight); === modified file 'src/extension/internal/text_reassemble.c' --- src/extension/internal/text_reassemble.c 2013-03-19 02:22:04 +0000 +++ src/extension/internal/text_reassemble.c 2013-05-14 23:06:10 +0000 @@ -67,8 +67,8 @@ File: text_reassemble.c -Version: 0.0.6 -Date: 12-MAR-2013 +Version: 0.0.10 +Date: 06-MAY-2013 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2013 David Mathog and California Institute of Technology (Caltech) @@ -80,6 +80,115 @@ #include "text_reassemble.h" #include "uemf_utf.h" /* For a couple of text functions. Exact copy from libUEMF. */ +#include + +/* Code generated by make_ucd_mn_table.c using: + cat mnlist.txt | ./make_ucd_mn_table >generated.c +*/ +#include +int is_mn_unicode(int test){ +#define MN_TEST_LIMIT 921600 +#define N_SPAGES 225 +#define N_CPAGES 192 +#define N_CPDATA 344 +#define C_PER_S 16 + uint8_t superpages[N_SPAGES]={ + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B}; + + uint8_t cpages[N_CPAGES]={ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x19, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x25, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x28, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + uint32_t cpage_data[N_CPDATA]={ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000F8, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFE0000, 0xBFFFFFFF, 0x000000B6, 0x00000000, + 0x07FF0000, 0x00000000, 0xFFFFF800, 0x00010000, 0x00000000, 0x00000000, 0x9FC00000, 0x00003D9F, + 0x00020000, 0xFFFF0000, 0x000007FF, 0x00000000, 0x00000000, 0x0001FFC0, 0x00000000, 0x000FF800, + 0xFBC00000, 0x00003EEF, 0x0E000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7FFFFFF0, + 0x00000007, 0x14000000, 0x00FE21FE, 0x0000000C, 0x00000002, 0x10000000, 0x0000201E, 0x0000000C, + 0x00000006, 0x10000000, 0x00023986, 0x00230000, 0x00000006, 0x10000000, 0x000021BE, 0x0000000C, + 0x00000002, 0x90000000, 0x0040201E, 0x0000000C, 0x00000004, 0x00000000, 0x00002001, 0x00000000, + 0x00000000, 0xC0000000, 0x00603DC1, 0x0000000C, 0x00000000, 0x90000000, 0x00003040, 0x0000000C, + 0x00000000, 0x00000000, 0x0000201E, 0x0000000C, 0x00000000, 0x00000000, 0x005C0400, 0x00000000, + 0x00000000, 0x07F20000, 0x00007F80, 0x00000000, 0x00000000, 0x1BF20000, 0x00003F00, 0x00000000, + 0x03000000, 0x02A00000, 0x00000000, 0x7FFE0000, 0xFEFFE0DF, 0x1FFFFFFF, 0x00000040, 0x00000000, + 0x00000000, 0x66FDE000, 0xC3000000, 0x001E0001, 0x20002064, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xE0000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x001C0000, 0x001C0000, 0x000C0000, 0x000C0000, 0x00000000, 0x3FB00000, 0x200FFE40, 0x00000000, + 0x00003800, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000200, 0x00000000, 0x00000000, + 0x00000000, 0x0E040187, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x01800000, 0x00000000, 0x7F400000, 0x9FF81FE5, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000000F, 0x17D00000, 0x00000004, 0x000FF800, 0x00000003, 0x00000B3C, 0x00000000, 0x0003A340, + 0x00000000, 0x00CFF000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFF70000, 0x001021FD, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xF000007F, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1FFF0000, 0x0001FFE2, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00038000, + 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, + 0x00000000, 0x00003C00, 0x00000000, 0x00000000, 0x06000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x3FF08000, 0x80000000, 0x00000000, 0x00000000, 0x00030000, + 0x00000844, 0x00000060, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000010, 0x0003FFFF, + 0x00000000, 0x00003FC0, 0x0003FF80, 0x00000000, 0x00000007, 0x13C80000, 0x00000000, 0x00000000, + 0x00000000, 0x00667E00, 0x00001008, 0x00000000, 0x00000000, 0xC19D0000, 0x00000002, 0x00403000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00002120, + 0x40000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000FFFF, 0x0000007F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20000000, + 0x0000F06E, 0x87000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000002, 0xFF000000, 0x0000007F, 0x00000000, 0x00000003, 0x06780000, 0x00000000, 0x00000000, + 0x00000007, 0x001FEF80, 0x00000000, 0x00000000, 0x00000003, 0x7FC00000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00BF2800, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00078000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xF8000380, 0x00000FE7, 0x00003C00, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x0000001C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF}; + + int result=0; + + int spage_idx; + int cpage_row, cpage_column, cpage_idx; + int cd_row, cd_column, cd_idx, cd_bit; + + if(test> 12; + cpage_row = superpages[spage_idx]; + cpage_column = (test>>8) & 15; + cpage_idx = C_PER_S*cpage_row + cpage_column; + cd_row = cpages[cpage_idx]; + cd_column = test>>5 & 7; + cd_idx = 8*cd_row + cd_column; + cd_bit = test & 31; + result = cpage_data[cd_idx] & (1 << cd_bit); + } + return(result); +} + + /** \brief Find a (sub)string in a caseinvariant manner, used for locating "Narrow" in font name @@ -87,7 +196,7 @@ \param string Text to search \param sub Text to find */ -int TR_findcasesub(char *string, char *sub){ +int TR_findcasesub(const char *string, const char *sub){ int i,j; int match=0; for(i=0; string[i]; i++){ @@ -103,9 +212,117 @@ } /** + \brief Constrouct a fontspec from a TCHUNK_SPECS and a fontname + \return Returns NULL on error, new fontspec on success + \param tsp pointer to TCHUNK_SPECS to use for information + \param fontname Fontname to use in the new fontspec +*/ + /* construct a font name */ +char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname){ + int newlen = 128 + strlen(fontname); /* too big, but not by much */ + char *newfs = NULL; + newfs = (char *) malloc(newlen); + sprintf(newfs,"%s:slant=%d:weight=%d:size=%lf:width=%d",fontname,tsp->italics,tsp->weight,tsp->fs,(tsp->co ? 75 : tsp->condensed)); + return(newfs); +} + + +/** + \brief Reconstrouct a fontspec by substituting a font name into an existing spec + \return Returns NULL on error, new fontspec on success + \param fontspec Original fontspec, only the name will be changed + \param fontname Fontname to substitute into the new fontspec +*/ + /* construct a font name */ +char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname){ + int colon; + int newlen = strlen(fontspec) + strlen(fontname) + 1; /* too big, but not by much */ + char *newfs = NULL; + newfs = (char *) malloc(newlen); + colon = strcspn(fontspec,":"); + if(colon){ sprintf(newfs,"%s%s",fontname,&fontspec[colon]); } + return(newfs); +} + +/** + \brief Find a font in the list that has a glyph for this character, change alternate to match + \return Returns 0 if no match or an error, else returns the glyph index in the new alternate font + \param fti pointer to the FT_INFO structure, may be modified if alternate font is added + \param efsp Pointer to a Pointer to the original FNT_SPECS struct. On return contains the FNT_SPECS corresponding to the glyph_index.. + \param wc Current character (32 bit int) +*/ +int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc){ + int glyph_index=0; /* this is the unknown character glyph */ + uint32_t i; + FcCharSet *cs; + FcResult result = FcResultMatch; + FcPattern *pattern, *fpat; + char *filename; + char *fontname; + char *newfontspec; + int fi_idx; + FNT_SPECS *fsp,*fsp2; + if(!fti || !efsp || !*efsp)return(0); + fsp = *efsp; + for(i=0;iused;i++){ /* first check in alts */ + fsp2 = &fti->fonts[fsp->alts[i].fi_idx]; /* these are in order of descending previous usage */ + glyph_index = FT_Get_Char_Index( fsp2->face, wc); /* we have the face, might as well check that directly */ + if (glyph_index){ /* found a glyph for the character in this font */ + (void) fsp_alts_weight(fsp, i); + *efsp = fsp2; + return(glyph_index); + } + } + + /* it was not in alts, now go through fontset and see if it is in there */ + for(i=1; i< (unsigned int) fsp->fontset->nfont;i++){ /* already know the primary does not have this character */ + result = FcPatternGetCharSet(fsp->fontset->fonts[i], FC_CHARSET, 0, &cs); + if(result != FcResultMatch) return(0); /* some terrible problem, this should never happen */ + if (FcCharSetHasChar(cs, wc)){ /* found a glyph for the character in this font */ + glyph_index = i; + + /* Do a lot of work to find the filename corresponding to the fontset entry. + None of these should ever fail, but if one does, return 0 + */ + if( + !(pattern = FcNameParse((const FcChar8 *)&(fsp->fontspec))) || + !FcConfigSubstitute(NULL, pattern, FcMatchPattern) + )return(0); + FcDefaultSubstitute(pattern); + if( + !(fpat = FcFontRenderPrepare(NULL, pattern, fsp->fontset->fonts[i])) || + (FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch) || + (FcPatternGetString( fsp->fontset->fonts[i], FC_FULLNAME, 0, (FcChar8 **)&fontname) != FcResultMatch) + )return(0); + + /* find the font (added from an unrelated fontset, for instance) or insert it as new */ + fi_idx = ftinfo_find_loaded_by_src(fti, (uint8_t *) filename); + if(fi_idx < 0){ + newfontspec = TR_reconstruct_fontspec((char *) fsp->fontspec, fontname); + fi_idx = ftinfo_load_fontname(fti, newfontspec); + free(newfontspec); + if(fi_idx < 0)return(0); /* This could happen if we run out of memory*/ + } + + /* add the new font index to the alts list on the (current) fsp. */ + (void) fsp_alts_insert(fsp, fi_idx); + + /* release FC's own memory related to this call that does not need to be kept around so that face will work */ + FcPatternDestroy(pattern); + + *efsp = &(fti->fonts[fi_idx]); + return(glyph_index); + } + } + + return(0); +} + +/** \brief Get the advance for the 32 bit character \return Returns -1 on error, or advance in units of 1/64th of a Point. + \param fti pointer to the FT_INFO structure, may be modified if alternate font is required \param fsp Pointer to FNT_SPECS struct. \param wc Current character (32 bit int) \param pc Previous character @@ -117,23 +334,31 @@ \param ymin If the pointer is defined, the value is adjusted if ymin of wc character is less than the current value. \param ymax If the pointer is defined, the value is adjusted if ymin of wc character is more than the current value. */ -int TR_getadvance(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax){ +int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax){ FT_Glyph glyph; int glyph_index; int advance=-1; FT_BBox bbox; + if(is_mn_unicode(wc))return(0); /* no advance on Unicode Mn characters */ + glyph_index = FT_Get_Char_Index( fsp->face, wc); - if (!FT_Load_Glyph( fsp->face, glyph_index, load_flags )){ - if ( !FT_Get_Glyph( fsp->face->glyph, &glyph ) ) { - advance = fsp->face->glyph->advance.x; - FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_UNSCALED, &bbox ); - if(ymin && (bbox.yMin < *ymin))*ymin=bbox.yMin; - if(ymax && (bbox.yMax > *ymax))*ymax=bbox.yMax; - if(pc)advance += TR_getkern2(fsp, wc, pc, kern_mode); - FT_Done_Glyph(glyph); + if(!glyph_index){ /* not in primary font, check alternates */ + glyph_index = TR_find_alternate_font(fti, &fsp, wc); + } + if(glyph_index){ + if (!FT_Load_Glyph( fsp->face, glyph_index, load_flags )){ + if ( !FT_Get_Glyph( fsp->face->glyph, &glyph ) ) { + advance = fsp->face->glyph->advance.x; + FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_UNSCALED, &bbox ); + if(ymin && (bbox.yMin < *ymin))*ymin=bbox.yMin; + if(ymax && (bbox.yMax > *ymax))*ymax=bbox.yMax; + if(pc)advance += TR_getkern2(fsp, wc, pc, kern_mode); + FT_Done_Glyph(glyph); + } } } + /* If there was no way to determine the width, this returns the error value */ return(advance); } @@ -164,7 +389,7 @@ } /** - \brief Get the kerning for a pair of 32 bit characters, where one is the last charcter in the previous text block, and the other is the first in the current text block. + \brief Get the kerning for a pair of 32 bit characters, where one is the last character in the previous text block, and the other is the first in the current text block. \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point. \param fsp Pointer to FNT_SPECS struct. \param tsp current text object @@ -214,17 +439,16 @@ */ double TR_baseline(TR_INFO *tri, int src, double *ymax, double *ymin){ double baseline=0; - double tmp; + volatile double tmp=0.0; /* This MUST be volatile */ double yheight; int last; int i; int trec; - FNT_SPECS *fsp; - static int depth=0; CX_INFO *cxi=tri->cxi; BR_INFO *bri=tri->bri; TP_INFO *tpi=tri->tpi; FT_INFO *fti=tri->fti; + FNT_SPECS *fsp; last = cxi->cx[src].kids.used - 1; switch (cxi->cx[src].type){ case TR_TEXT: @@ -242,19 +466,24 @@ } break; case TR_LINE: - for(i=last;i>=0;i--){ /* here last is the count of */ + for(i=last;i>=0;i--){ /* here last is the count of text objects in the complex */ trec = cxi->cx[src].kids.members[i]; fsp = &(fti->fonts[tpi->chunks[trec].fi_idx]); yheight = fsp->face->bbox.yMax - fsp->face->bbox.yMin; if(ymax){ - tmp = tpi->chunks[trec].fs * ((double)fsp->face->bbox.yMax/yheight); + tmp = tpi->chunks[trec].fs * (((double)fsp->face->bbox.yMax)/yheight); + /* gcc 4.6.3 had a bizarre optimization error for -O2 and -O3 where *ymax <= tmp was + not true when *ymax == tmp, as verified by examining the binary representations. + This was apparently due to retained excess precision. Making tmp volatile + forces it to be stored into a 64 bit location, dropping the extra 12 bits from + the 80 bit register. */ if(*ymax <= tmp){ *ymax = tmp; baseline = bri->rects[trec].yll - tpi->chunks[trec].boff; } } else if(ymin){ - tmp = tpi->chunks[trec].fs * ((double)-fsp->face->bbox.yMin/yheight); /* yMin in face is negative */ + tmp = tpi->chunks[trec].fs * (((double)-fsp->face->bbox.yMin)/yheight); /* yMin in face is negative */ if(*ymin <= tmp){ *ymin = tmp; baseline = bri->rects[trec].yll - tpi->chunks[trec].boff; @@ -266,7 +495,6 @@ case TR_PARA_LJ: case TR_PARA_CJ: case TR_PARA_RJ: - depth++; trec = cxi->cx[src].kids.members[last]; baseline = TR_baseline(tri, trec, ymax, ymin); break; @@ -301,8 +529,8 @@ int status = 0; CX_INFO *cxi = tri->cxi; TP_INFO *tpi = tri->tpi; - double ymax = 0.0; - double ymin = 0.0; + double ymax = DBL_MIN; + double ymin = DBL_MIN; double prevbase; double thisbase; double weight; @@ -374,10 +602,7 @@ int ftinfo_make_insertable(FT_INFO *fti){ int status=0; if(!fti)return(2); - if(fti->used < fti->space){ - /* already insertable */ - } - else { + if(fti->used >= fti->space){ fti->space += ALLOCINFO_CHUNK; if((fti->fonts = (FNT_SPECS *) realloc(fti->fonts, fti->space * sizeof(FNT_SPECS) ))){ memset(&fti->fonts[fti->used],0,(fti->space - fti->used)*sizeof(FNT_SPECS)); @@ -416,22 +641,12 @@ \returns NULL. */ FT_INFO *ftinfo_release(FT_INFO *fti){ - int i; - if(fti){ - for(i=0;iused;i++){ - FT_Done_Face(fti->fonts[i].face); /* release memory for face controlled by FreeType */ - free(fti->fonts[i].file); /* release memory holding copies of paths */ - free(fti->fonts[i].fname); /* release memory holding copies of font names */ - FcPatternDestroy(fti->fonts[i].fpat); /* release memory for FontConfit fpats */ - } - free(fti->fonts); - FT_Done_FreeType(fti->library); /* release all other FreeType memory */ - free(fti); - FcFini(); /* shut down FontConfig, release memory, patterns must have already been released or boom! */ - } + (void) ftinfo_clear(fti); + FcFini(); /* shut down FontConfig, release memory, patterns must have already been released or boom! */ return NULL; } + /** \brief Clear an FT_INFO structure. Release all Freetype memory but does not release Fontconfig. This would be called in preference to ftinfo_release() if some other part of the program needed @@ -441,13 +656,17 @@ \returns NULL. */ FT_INFO *ftinfo_clear(FT_INFO *fti){ - int i; + uint32_t i; + FNT_SPECS *fsp; if(fti){ for(i=0;iused;i++){ - FT_Done_Face(fti->fonts[i].face); /* release memory for face controlled by FreeType */ - free(fti->fonts[i].file); /* release memory holding copies of paths */ - free(fti->fonts[i].fname); /* release memory holding copies of font names */ - FcPatternDestroy(fti->fonts[i].fpat); /* release memory for FontConfit fpats */ + fsp = &(fti->fonts[i]); + FT_Done_Face(fsp->face); /* release memory for face controlled by FreeType */ + free(fsp->file); /* release memory holding copies of paths */ + free(fsp->fontspec); /* release memory holding copies of font names */ + FcPatternDestroy(fsp->fpat); /* release memory for FontConfig fpats */ + FcFontSetDestroy(fti->fonts[i].fontset); + if(fsp->alts){ free(fsp->alts); } } free(fti->fonts); FT_Done_FreeType(fti->library); /* release all other FreeType memory */ @@ -458,6 +677,227 @@ /** + \brief Find the loaded font matching fontspec + \returns index of font on success, -1 if not found + \param tri pointer to the TR_INFO structure. + \param fontspec UTF-8 description of the font, as constructed in trinfo_load_fontname +*/ + +int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fontspec){ + uint32_t i; + int status = -1; + /* If it is already loaded, do not load it again */ + for(i=0;iused;i++){ + if(0==strcmp((char *) fti->fonts[i].fontspec, (char *)fontspec)){ + status=i; + break; + } + } + return(status); +} + +/** + \brief Find the loaded font matching the source file + \returns index of font on success, -1 if not found + \param tri pointer to the TR_INFO structure. + \param filename UTF-8 file name for the font +*/ + +int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename){ + uint32_t i; + int status = -1; + /* If it is already loaded, do not load it again */ + for(i=0;iused;i++){ + if(0==strcmp((char *) fti->fonts[i].file, (char *) filename)){ + status=i; + break; + } + } + return(status); +} + + +/** + \brief Load a (new) font by name into a TR_INFO structure or find it if it is already loaded + \returns fi_idx of inserted (or found) font on success, <0 on error. + \param fti pointer to the FT_INFO structure. + \param fontname UTF-8 font name + \param fontspec UTF-8 font specification used for query string. +*/ + +int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec){ + FcPattern *pattern, *fpat; + FcFontSet *fontset; + FcResult result = FcResultMatch; + char *filename; + double fd; + FNT_SPECS *fsp; + int status; + int fi_idx; + + if(!fti)return(-1); + + /* If it is already loaded, do not load it again */ + if((status = ftinfo_find_loaded_by_spec(fti, (uint8_t *) fontspec))>=0){ + return(status); + } + + ftinfo_make_insertable(fti); + fi_idx = fti->used; + + if(!(pattern = FcNameParse((const FcChar8 *)fontspec)) )return(2); + if(!FcConfigSubstitute(NULL, pattern, FcMatchPattern) )return(3); + FcDefaultSubstitute(pattern); + /* get a fontset, trimmed to only those with new glyphs as needed, so that missing glyph's may be handled */ + if(!(fontset = FcFontSort (NULL,pattern, FcTrue, NULL, &result)) + || result != FcResultMatch)return(4); + if(!(fpat = FcFontRenderPrepare(NULL, pattern, fontset->fonts[0])) )return(405); + if(FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch)return(5); + if(FcPatternGetDouble( fpat, FC_SIZE, 0, &fd) != FcResultMatch)return(6); + + /* copy these into memory for external use */ + fsp = &(fti->fonts[fti->used]); + fsp->fontset = fontset; + fsp->alts = NULL; /* Initially no links to alternate fonts */ + fsp->space = 0; + fsp->file = (uint8_t *) U_strdup((char *) filename); + fsp->fontspec = (uint8_t *) U_strdup((char *) fontspec); + fsp->fpat = fpat; + fsp->fsize = fd; + + /* release FC's own memory related to this call that does not need to be kept around so that face will work */ + FcPatternDestroy(pattern); + + /* get the current face */ + if(FT_New_Face( fti->library, (const char *) fsp->file, 0, &(fsp->face) )){ return(8); } + + if(FT_Set_Char_Size( + fsp->face, /* handle to face object */ + 0, /* char_width in 1/64th of points */ + fd*64, /* char_height in 1/64th of points */ + 72, /* horizontal device resolution, DPI */ + 72) /* vebrical device resolution, DPI */ + ){ return(9); } + + /* The space advance is needed in various places. Get it now, and get it in the font units, + so that it can be scaled later with the text size */ + status = TR_getadvance(fti, fsp,' ',0,FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, FT_KERNING_UNSCALED, NULL, NULL); + if(status < 0)return(7); + fsp->spcadv = ((double) status)/(64.0); + + fti->used++; + +/* + char *fs; + int fb; + if(FcPatternGetBool( fpat, FC_OUTLINE, 0, &fb)== FcResultMatch){ printf("outline: %d\n",fb);fflush(stdout); } + if(FcPatternGetBool( fpat, FC_SCALABLE, 0, &fb)== FcResultMatch){ printf("scalable: %d\n",fb);fflush(stdout); } + if(FcPatternGetDouble( fpat, FC_DPI, 0, &fd)== FcResultMatch){ printf("DPI: %lf\n",fd);fflush(stdout); } + if(FcPatternGetInteger( fpat, FC_FONTVERSION, 0, &fb)== FcResultMatch){ printf("fontversion: %d\n",fb);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FULLNAME , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAME : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FAMILY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_STYLE , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLE : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FOUNDRY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FOUNDRY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FAMILYLANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILYLANG : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_STYLELANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLELANG : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FULLNAMELANG, 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAMELANG: %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_CAPABILITY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("CAPABILITY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FONTFORMAT , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FONTFORMAT : %s\n",fs);fflush(stdout); } +*/ + + return(fi_idx); +} + +/** + \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code. + \param tri pointer to the TR_INFO structure. +*/ +void ftinfo_dump(const FT_INFO *fti){ + uint32_t i,j; + FNT_SPECS *fsp; + printf("fti space: %d\n",fti->space); + printf("fti used: %d\n",fti->used); + for(i=0; i< fti->used; i++){ + fsp = &(fti->fonts[i]); + printf("fti font: %6d space: %6d used: %6d spcadv %8lf fsize %8lf \n",i,fsp->space,fsp->used,fsp->spcadv,fsp->fsize); + printf(" file: %s\n",fsp->file); + printf(" fspc: %s\n",fsp->fontspec); + for(j=0;jused;j++){ + printf(" alts: %6d fi_idx: %6d wgt: %6d\n",j,fsp->alts[j].fi_idx,fsp->alts[j].weight); + } + } + +} + +/** + \brief Make the FNT_SPECS alts structure insertable. Adds storage as needed. + \param fti pointer to the FT_INFO structure + \returns 0 on success, !0 on error. +*/ +int fsp_alts_make_insertable(FNT_SPECS *fsp){ + int status=0; + if(!fsp)return(2); + if(fsp->used >= fsp->space){ + fsp->space += ALLOCINFO_CHUNK; + if((fsp->alts = (ALT_SPECS *) realloc(fsp->alts, fsp->space * sizeof(ALT_SPECS) ))){ + memset(&fsp->alts[fsp->used],0,(fsp->space - fsp->used)*sizeof(ALT_SPECS)); + } + else { + status=1; + } + } + return(status); +} + + +/** + \brief Insert a new ALT_SPECS into the FNT_SPECS alts list. + \param fsp pointer to the FNT_SPECS structure. + \param fi_idx font index to add to the alts list + \returns 0 on success, !0 on error. +*/ +int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx){ + int status=1; + ALT_SPECS alt; + if(!fsp)return(3); + alt.fi_idx = fi_idx; + alt.weight = 1; /* new ones start with this weight, it can only go up */ + if(!(status = fsp_alts_make_insertable(fsp))){ + fsp->alts[fsp->used] = alt; + fsp->used++; + } + return(status); +} + +/** + \brief Increment the weight of an alts entry by 1, readjust order if necessary + \param fsp pointer to the FNT_SPECS structure. + \param idx index of the alts entry to increment + \returns 0 on success, !0 on error. +*/ +int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx){ + uint32_t i; + ALT_SPECS alt; + if(!fsp)return(1); + if(!fsp->used)return(2); + if(a_idx >= fsp->used)return(3); + /* If a counter hits the limit divide all counts in half. */ + if(fsp->alts[a_idx].weight == UINT32_MAX){ + for(i=0; iused; i++){ fsp->alts[i].weight /= 2; } + } + fsp->alts[a_idx].weight++; + for(i=a_idx; i>0; i--){ + if(fsp->alts[i-1].weight >= fsp->alts[a_idx].weight)break; + alt = fsp->alts[i-1]; + fsp->alts[i-1] = fsp->alts[a_idx]; + fsp->alts[a_idx] = alt; + } + return(0); +} + + + +/** \brief Make a CHILD_SPECS structure insertable. Adds storage as needed. \param csp pointer to the CHILD_SPECS structure \returns 0 on success, !0 on error. @@ -465,10 +905,7 @@ int csp_make_insertable(CHILD_SPECS *csp){ int status=0; if(!csp)return(2); - if(csp->used < csp->space){ - /* already insertable */ - } - else { + if(csp->used >= csp->space){ csp->space += ALLOCINFO_CHUNK; if((csp->members = (int *) realloc(csp->members, csp->space * sizeof(int) ))){ memset(&csp->members[csp->used],0,(csp->space - csp->used)*sizeof(int)); @@ -505,8 +942,8 @@ \returns 0 on success, !0 on error. */ int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src){ - int i; - int status=1; + uint32_t i; + int status=1; if(!dst)return(2); if(!src)return(3); for(i=0;iused;i++){ @@ -529,6 +966,15 @@ } } +/** + \brief Clear a CHILD_SPECS structure, making all allocated slots usable. Does not release associated memory. + \param csp pointer to the CHILD_SPECS structure. + \returns NULL. +*/ +void csp_clear(CHILD_SPECS *csp){ + csp->used = 0; +} + /** \brief Initialize an CX_INFO structure. Holds complexes (multiple text objects in known positions and order.) @@ -553,10 +999,7 @@ */ int cxinfo_make_insertable(CX_INFO *cxi){ int status=0; - if(cxi->used < cxi->space){ - /* already insertable */ - } - else { + if(cxi->used >= cxi->space){ cxi->space += ALLOCINFO_CHUNK; if((cxi->cx = (CX_SPECS *) realloc(cxi->cx, cxi->space * sizeof(CX_SPECS) ))){ memset(&cxi->cx[cxi->used],0,(cxi->space - cxi->used)*sizeof(CX_SPECS)); @@ -617,19 +1060,37 @@ int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type){ int status =1; if(!cxi)return(2); - if(dst < 0 || dst >= cxi->used)return(3); - if(src < 0)return(4); + if(!cxi->used)return(3); + if(dst < 0 || dst >= (int) cxi->used)return(4); + if(src < 0)return(5); cxi->cx[dst].type = type; status = csp_merge(&(cxi->cx[dst].kids), &(cxi->cx[src].kids)); return(status); } /** + \brief Trim the last complex from thelist of complexes. + \param cxi pointer to the CX_INFO structure (complexes). + \returns 0 on success, !0 on error. +*/ +int cxinfo_trim(CX_INFO *cxi){ + int status = 0; + int last ; + if(!cxi)return(1); + if(!cxi->used)return(2); + last = cxi->used - 1; + csp_clear(&(cxi->cx[last].kids)); + cxi->used--; + return(status); +} + + +/** \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code. \param tri pointer to the TR_INFO structure. */ -void cxinfo_dump(TR_INFO *tri){ - int i,j,k; +void cxinfo_dump(const TR_INFO *tri){ + uint32_t i,j,k; CX_INFO *cxi = tri->cxi; BR_INFO *bri = tri->bri; TP_INFO *tpi = tri->tpi; @@ -641,17 +1102,23 @@ printf("cxi phase1: %d\n",cxi->phase1); printf("cxi lines: %d\n",cxi->lines); printf("cxi paras: %d\n",cxi->paras); + printf("cxi xy: %lf , %lf\n",tri->x,tri->y); for(i=0;iused;i++){ csp = &(cxi->cx[i]); bsp = &(bri->rects[csp->rt_cidx]); - printf("cxi cx[%d] type:%d rt_tidx:%d kids_used:%d kids:space:%d\n",i, csp->type, csp->rt_cidx, csp->kids.used, csp->kids.space); + printf("cxi cx[%d] type:%d rt_tidx:%d kids_used:%d kids_space:%d\n",i, csp->type, csp->rt_cidx, csp->kids.used, csp->kids.space); printf("cxi cx[%d] br (LL,UR) (%lf,%lf),(%lf,%lf)\n",i,bsp->xll,bsp->yll,bsp->xur,bsp->yur); for(j=0;jkids.used;j++){ k = csp->kids.members[j]; bsp = &(bri->rects[k]); if(csp->type == TR_TEXT || csp->type == TR_LINE){ - printf("cxi cx[%d] member:%d tp_idx:%d rt_tidx:%d br (LL,UR) (%8.3lf,%8.3lf),(%8.3lf,%8.3lf) text:<%s>\n",i, j, k, tpi->chunks[k].rt_tidx, bsp->xll,bsp->yll,bsp->xur,bsp->yur, tpi->chunks[k].string); + printf("cxi cx[%d] member:%3d tp_idx:%3d ldir:%d rt_tidx:%3d br (LL,UR) (%8.3lf,%8.3lf),(%8.3lf,%8.3lf) xy (%8.3lf,%8.3lf) kern (%8.3lf,%8.3lf) text:<%s> decor:%5.5x\n", + i, j, k, tpi->chunks[k].ldir, tpi->chunks[k].rt_tidx, + bsp->xll,bsp->yll,bsp->xur,bsp->yur, + tpi->chunks[k].x, tpi->chunks[k].y, + tpi->chunks[k].xkern, tpi->chunks[k].ykern, + tpi->chunks[k].string, tpi->chunks[k].decoration ); } else { /* TR_PARA_* */ printf("cxi cx[%d] member:%d cx_idx:%d\n",i, j, k); @@ -669,7 +1136,7 @@ \returns NULL. */ CX_INFO *cxinfo_release(CX_INFO *cxi){ - int i; + uint32_t i; if(cxi){ for(i=0;iused;i++){ csp_release(&cxi->cx[i].kids); } free(cxi->cx); @@ -721,13 +1188,16 @@ \param tpi pointer to the TP_INFO structure \param tsp pointer to the TCHUNK_SPECS structure */ -int tpinfo_insert(TP_INFO *tpi, TCHUNK_SPECS *tsp){ +int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp){ int status=1; + TCHUNK_SPECS *ltsp; if(!tpi)return(2); if(!tsp)return(3); if(!(status = tpinfo_make_insertable(tpi))){ - memcpy(&(tpi->chunks[tpi->used]),tsp,sizeof(TCHUNK_SPECS)); - if(tsp->co)tpi->chunks[tpi->used].condensed = 75; /* Narrow was set in the font name */ + ltsp = &(tpi->chunks[tpi->used]); + memcpy(ltsp,tsp,sizeof(TCHUNK_SPECS)); + if(tsp->co)ltsp->condensed = 75; /* Narrow was set in the font name */ + ltsp->xkern = ltsp->ykern = 0.0; /* kerning will be calculated from the derived layout */ tpi->used++; } return(status); @@ -740,7 +1210,7 @@ \param tpi pointer to the TP_INFO structure. */ TP_INFO *tpinfo_release(TP_INFO *tpi){ - int i; + uint32_t i; if(tpi){ for(i=0;iused;i++){ free(tpi->chunks[i].string); } @@ -787,7 +1257,7 @@ \param bri pointer to the BR_INFO structure \param element pointer to the BRECT_SPECS structure */ -int brinfo_insert(BR_INFO *bri, BRECT_SPECS *element){ +int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element){ int status=1; if(!bri)return(2); if(!(status=brinfo_make_insertable(bri))){ @@ -806,8 +1276,9 @@ */ int brinfo_merge(BR_INFO *bri, int dst, int src){ if(!bri)return(1); - if(dst<0 || dst>= bri->used)return(2); - if(src<0 || src>= bri->used)return(3); + if(!bri->used)return(2); + if(dst<0 || dst >= (int) bri->used)return(3); + if(src<0 || src >= (int) bri->used)return(4); bri->rects[dst].xll = TEREMIN(bri->rects[dst].xll, bri->rects[src].xll); bri->rects[dst].yll = TEREMAX(bri->rects[dst].yll, bri->rects[src].yll); /* MAX because Y is positive DOWN */ bri->rects[dst].xur = TEREMAX(bri->rects[dst].xur, bri->rects[src].xur); @@ -841,13 +1312,14 @@ \param rp_dst Pointer to edge padding values for dst. \param rp_src Pointer to edge padding values for src. */ -int brinfo_overlap(BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src){ +int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src){ int status; BRECT_SPECS *br_dst; BRECT_SPECS *br_src; if(!bri || !rp_dst || !rp_src)return(2); - if(dst<0 || dst>= bri->used)return(3); - if(src<0 || src>= bri->used)return(4); + if(!bri->used)return(3); + if(dst<0 || dst>= (int) bri->used)return(4); + if(src<0 || src>= (int) bri->used)return(5); br_dst=&bri->rects[dst]; br_src=&bri->rects[src]; if( /* Test all conditions that exclude overlap, if any are true, then no overlap */ @@ -908,6 +1380,36 @@ } /** + \brief Check for a text element upstream from the start element and in the reversed direction. + \returns 0 on success (not upstream), 1 if upstream, anything else is an error. + \param bri pointer to the BR_INFO structure + \param dst index of the destination bounding rectangle. + \param src index of the source bounding rectangle. + \param ddir direction of dst + \param sdir direction of src +*/ + +int brinfo_upstream(BR_INFO *bri, int dst, int src, int ddir, int sdir){ + int status=0; + BRECT_SPECS *br_dst; + BRECT_SPECS *br_src; + if(!bri)return(2); + if(!bri->used)return(3); + if(dst<0 || dst>= (int) bri->used)return(4); + if(src<0 || src>= (int) bri->used)return(5); + br_dst=&bri->rects[dst]; + br_src=&bri->rects[src]; + if( ddir == LDIR_RL && sdir == LDIR_LR){ + if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; } + } + else if( ddir == LDIR_LR && sdir == LDIR_RL){ + if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; } + } + return(status); +} + + +/** \brief Try to deduce justification of a paragraph from the bounding rectangles for two successive lines. \returns one of TR_PARA_ UJ (unknown justified), LJ, CJ, or RJ (left, center, or right justified). \param bri pointer to the BR_INFO structure @@ -917,7 +1419,7 @@ \param type Preexisting justification for dst, if any. Justification of dst and src must match this or TR_PARA_UJ is returned even if dst and src have some (other) alignment. */ -enum tr_classes brinfo_pp_alignment(BR_INFO *bri, int dst, int src, double slop, enum tr_classes type){ +enum tr_classes brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type){ enum tr_classes newtype; BRECT_SPECS *br_dst = & bri->rects[dst]; BRECT_SPECS *br_src = & bri->rects[src]; @@ -1073,91 +1575,6 @@ return(tri); } -/** - \brief Load a (new) font by name into a TR_INFO structure. - \returns index of font. If the font was already loaded return the existing index. - \param tri pointer to the TR_INFO structure. - \param fontname UTF-8 font name - \param tsp pointer to the TCHUNK_SPECS structure - used to construct a font query string. -*/ - -int trinfo_load_fontname(TR_INFO *tri, uint8_t *fontname, TCHUNK_SPECS *tsp){ - FcPattern *pattern, *fpat; - FcResult result = FcResultMatch; - char *filename; - double fd; - int i; - FT_INFO *fti; - char buffer[512]; /* big enough */ - FNT_SPECS *fsp; - - if(!tri || !(tri->fti))return(1); - - fti = tri->fti; - /* construct a font name */ - sprintf(buffer,"%s:slant=%d:weight=%d:size=%lf:width=%d",fontname,tsp->italics,tsp->weight,tsp->fs,(tsp->co ? 75 : tsp->condensed)); - - for(i=0;iused;i++){ - if(0==strcmp((char *) fti->fonts[i].fname,buffer)){ - tsp->fi_idx=i; - return(0); - } - } - - ftinfo_make_insertable(fti); - tsp->fi_idx = fti->used; - - if((pattern = FcNameParse((const FcChar8 *)buffer)) == NULL)return(2); - if(!FcConfigSubstitute(NULL, pattern, FcMatchPattern))return(3); - FcDefaultSubstitute(pattern); - if((fpat = FcFontMatch(NULL, pattern, &result)) == NULL || result != FcResultMatch)return(4); - if(FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch)return(5); - if(FcPatternGetDouble( fpat, FC_SIZE, 0, &fd) != FcResultMatch)return(7); - - /* copy these into memory for external use */ - fsp = &(fti->fonts[fti->used]); - fsp->file = (uint8_t *) U_strdup((char *) filename); - fsp->fname = (uint8_t *) U_strdup((char *) buffer); - fsp->fpat = fpat; - fsp->fsize = fd; - - /* release FC's own memory related to this call that does not need to be kept around so that face will work */ - FcPatternDestroy(pattern); - - /* get the face */ - if(FT_New_Face( fti->library, (const char *) fsp->file, 0, &(fsp->face) )){ return(8); } - - if(FT_Set_Char_Size( - fsp->face, /* handle to face object */ - 0, /* char_width in 1/64th of points */ - fd*64, /* char_height in 1/64th of points */ - 72, /* horizontal device resolution, DPI */ - 72) /* vebrical device resolution, DPI */ - ){ return(9); } - - fti->used++; - -/* - char *fs; - int fb; - if(FcPatternGetBool( fpat, FC_OUTLINE, 0, &fb)== FcResultMatch){ printf("outline: %d\n",fb);fflush(stdout); } - if(FcPatternGetBool( fpat, FC_SCALABLE, 0, &fb)== FcResultMatch){ printf("scalable: %d\n",fb);fflush(stdout); } - if(FcPatternGetDouble( fpat, FC_DPI, 0, &fd)== FcResultMatch){ printf("DPI: %lf\n",fd);fflush(stdout); } - if(FcPatternGetInteger( fpat, FC_FONTVERSION, 0, &fb)== FcResultMatch){ printf("fontversion: %d\n",fb);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FULLNAME , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAME : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FAMILY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILY : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_STYLE , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLE : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FOUNDRY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FOUNDRY : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FAMILYLANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILYLANG : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_STYLELANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLELANG : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FULLNAMELANG, 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAMELANG: %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_CAPABILITY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("CAPABILITY : %s\n",fs);fflush(stdout); } - if(FcPatternGetString( fpat, FC_FONTFORMAT , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FONTFORMAT : %s\n",fs);fflush(stdout); } -*/ - - return(0); -} - /** \brief Set the quantization error value for a TR_INFO structure. @@ -1239,14 +1656,11 @@ \param tri pointer to a TR_INFO structure \param src Pointer to a text string. */ -int trinfo_append_out(TR_INFO *tri, char *src){ +int trinfo_append_out(TR_INFO *tri, const char *src){ size_t slen; if(!src)return(-1); slen = strlen(src); - if(tri->outused + (int) slen + 1 < tri->outspace){ - /* already insertable */ - } - else { + if(tri->outused + (int) slen + 1 >= tri->outspace){ tri->outspace += TEREMAX(ALLOCOUT_CHUNK,slen+1); if(!(tri->out = realloc(tri->out, tri->outspace )))return(-1); } @@ -1265,7 +1679,7 @@ \param flags special processing flags: TR_EMFBOT calculate Y coordinates of ALIBOT object compatible with EMF files TA_BOTTOM alignment. */ -int trinfo_load_textrec(TR_INFO *tri, TCHUNK_SPECS *tsp, double escapement, int flags){ +int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags){ int status; double x,y,xe; @@ -1285,12 +1699,14 @@ if(!tri)return(1); if(!tsp)return(2); if(!tsp->string)return(3); - fti = tri->fti; - tpi = tri->tpi; - bri = tri->bri; - idx = tsp->fi_idx; - taln = tsp->taln; - if(idx <0 || idx >= tri->fti->used)return(4); + fti = tri->fti; + tpi = tri->tpi; + bri = tri->bri; + idx = tsp->fi_idx; + taln = tsp->taln; + if(!fti->used)return(4); + if(idx <0 || idx >= (int) fti->used)return(5); + fsp = &(fti->fonts[idx]); if(!tri->dirty){ tri->x = tsp->x; @@ -1318,10 +1734,11 @@ tpi->chunks[current].x = x * cos(escapement) - y * sin(escapement); /* coordinate transformation */ tpi->chunks[current].y = x * sin(escapement) + y * cos(escapement); - fsp = &(fti->fonts[idx]); /* Careful! face bbox does NOT scale with FT_Set_Char_Size printf("Face idx:%d bbox: xMax/Min:%ld,%ld yMax/Min:%ld,%ld UpEM:%d asc/des:%d,%d height:%d size:%lf\n", - idx, fsp->face->bbox.xMax,fsp->face->bbox.xMin,fsp->face->bbox.yMax,fsp->face->bbox.yMin, + idx, + fsp->face->bbox.xMax,fsp->face->bbox.xMin, + fsp->face->bbox.yMax,fsp->face->bbox.yMin, fsp->face->units_per_EM,fsp->face->ascender,fsp->face->descender,fsp->face->height,fsp->fsize); */ @@ -1330,17 +1747,22 @@ text32 = U_Latin1ToUtf32le((char *) tsp->string,0,NULL); if(!text32)return(5); } - fsp->spcadv = 0.0; /* baseline advance is independent of character orientation */ for(xe=0.0, prev=0, tptr=text32; *tptr; tptr++){ - status = TR_getadvance(fsp, *tptr, (tri->use_kern ? prev: 0), tri->load_flags, tri->kern_mode, &ymin, &ymax); + status = TR_getadvance(fti, fsp, *tptr, (tri->use_kern ? prev: 0), tri->load_flags, tri->kern_mode, &ymin, &ymax); if(status>=0){ xe += ((double) status)/64.0; - if(*tptr==' ')fsp->spcadv = ((double) status)/64.0; } else { return(6); } prev=*tptr; } + + /* Some glyphs in fonts have no vertical extent, for instance, Hebrew glyphs in Century Schoolbook L. + Use the 3/4 of the font size as a (very bad) approximation for the actual values. */ + if(ymin==0 && ymax==0){ + ymax = 0.75 * fsp->fsize * 64.0; + } + asc = ((double) (ymax))/64.0; dsc = ((double) (ymin))/64.0; /* This is negative */ /* This did not work very well because the ascender/descender went well beyond the actual characters, causing @@ -1350,22 +1772,12 @@ */ free(text32); - - /* get the advance on a space if it has not already been set */ - if(fsp->spcadv==0.0){ - status = TR_getadvance(fsp,' ',0, tri->load_flags, tri->kern_mode, NULL, NULL); - if(status>=0){ fsp->spcadv = ((double) status)/64.0; } - else { return(7); } - } /* find the font ascender descender (general one, not specific for current text) */ fasc = ((double) (fsp->face->ascender) )/64.0; fdsc = ((double) (fsp->face->descender))/64.0; - if(tri->load_flags & FT_LOAD_NO_SCALE){ - xe *= tsp->fs/32.0; - fsp->spcadv *= tsp->fs/32.0; - } + if(tri->load_flags & FT_LOAD_NO_SCALE) xe *= tsp->fs/32.0; /* now place the rectangle using ALN information */ if( taln & ALIHORI & ALILEFT ){ @@ -1380,6 +1792,7 @@ bsp.xll = tpi->chunks[current].x - xe; bsp.xur = tpi->chunks[current].x; } + tpi->chunks[current].ldir = tsp->ldir; if(tri->load_flags & FT_LOAD_NO_SCALE){ asc *= tsp->fs/32.0; @@ -1458,9 +1871,6 @@ double x = tri->x; double y = tri->y; double dx,dy; - double lastx = 0.0; - double lasty = 0.0; - double qsp; double esc; double recenter; /* horizontal offset to set things up correctly for CJ and RJ text, is 0 for LJ*/ double lineheight=1.25; @@ -1470,22 +1880,26 @@ BR_INFO *bri=tri->bri; /* bounding Rectangle Info storage */ CX_INFO *cxi=tri->cxi; /* Complexes deduced for this text */ TCHUNK_SPECS *tsp; /* current text object */ - TCHUNK_SPECS *ptsp; /* previous text object in the same line as current text object, if any */ - FNT_SPECS *fsp; CX_SPECS *csp; CX_SPECS *cline_sp; - int i,j,k,jdx,kdx; - int status; + unsigned int i,j,k,jdx,kdx; + int ldir; char obuf[1024]; /* big enough for style and so forth */ + char cbuf[16]; /* big enough for one hex color */ char stransform[128]; - double newx,newy; + double newx,newy,tmpx; + uint32_t utmp; +/* +#define DBG_TR_PARA 0 +#define DBG_TR_INPUT 1 +*/ /* The debug section below is difficult to see if usebk is anything other than BKCLR_NONE */ -#if defined(DBG_TR_PARA) || defined(DBG_TR_INPUT) /* enable debugging code, writes extra information into SVG */ +#if DBG_TR_PARA || DBG_TR_INPUT /* enable debugging code, writes extra information into SVG */ /* put rectangles down for each text string - debugging!!! This will not work properly for any Narrow fonts */ - esc = tri->esc; - esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ sprintf(stransform,"transform=\"matrix(%lf,%lf,%lf,%lf,%lf,%lf)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y); for(i=cxi->phase1; iused;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */ csp = &(cxi->cx[i]); @@ -1494,10 +1908,11 @@ for(k=0; kcx[jdx].kids.used; k++){ /* over all members of the phase1 complex */ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */ tsp = &tpi->chunks[kdx]; + ldir = tsp->ldir; if(!j && !k){ -#ifdef DBG_TR_PARA +#if DBG_TR_PARA TRPRINT(tri, "rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll)); TRPRINT(tri, obuf); sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur)); @@ -1508,16 +1923,16 @@ TRPRINT(tri, "/>\n"); #endif /* DBG_TR_PARA */ } -#ifdef DBG_TR_INPUT /* debugging code, this section writes the original text objects */ - newx = 1.25*(bri->rects[tsp->rt_tidx].xll); +#if DBG_TR_INPUT /* debugging code, this section writes the original text objects */ + newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll); newy = 1.25*(bri->rects[tsp->rt_tidx].yur); TRPRINT(tri, "rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll)); TRPRINT(tri, obuf); sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur)); TRPRINT(tri, obuf); - sprintf(obuf,"x=\"%lf\" y=\"%lf\"\n",newx,newy); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\n",1.25*(bri->rects[tsp->rt_tidx].xll),newy); TRPRINT(tri, obuf); TRPRINT(tri, stransform); TRPRINT(tri, "/>\n"); @@ -1538,9 +1953,10 @@ TRPRINT(tri, obuf); sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed")); TRPRINT(tri, obuf); - cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fname,":"); - fti->fonts[tsp->fi_idx].fname[cutat]='\0'; - sprintf(obuf,"font-family:%s;",fti->fonts[tsp->fi_idx].fname); + sprintf(obuf,"text-anchor:%s;",(tsp->ldir == LDIR_RL ? "end" : "start")); + TRPRINT(tri, obuf); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec); TRPRINT(tri, obuf); sprintf(obuf,"\n\">%s\n",tsp->string); TRPRINT(tri, obuf); @@ -1552,8 +1968,8 @@ if(tri->usebk){ - esc = tri->esc; - esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ sprintf(stransform,"transform=\"matrix(%lf,%lf,%lf,%lf,%lf,%lf)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y); for(i=cxi->phase1; iused;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */ @@ -1579,6 +1995,7 @@ for(k=0; kcx[jdx].kids.used; k++){ /* over all members of the phase1 complex */ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */ tsp = &tpi->chunks[kdx]; + ldir = tsp->ldir; if(!j && !k){ if(tri->usebk == BKCLR_ALL){ TRPRINT(tri, "usebk == BKCLR_FRAG){ - newx = 1.25*(bri->rects[tsp->rt_tidx].xll); + newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll); newy = 1.25*(bri->rects[tsp->rt_tidx].yur); TRPRINT(tri, "bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue); @@ -1616,12 +2033,12 @@ } - tsp=tpi->chunks; + tsp = tpi->chunks; /* over all complex members from phase2. Paragraphs == TR_PARA_* */ for(i=cxi->phase1; iused;i++){ - csp = &(cxi->cx[i]); - esc = tri->esc; - esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + csp = &(cxi->cx[i]); + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ /* over all members of the present Paragraph. Each of these is a line and a phase 1 complex. It may be either TR_TEXT or TR_LINE */ @@ -1630,37 +2047,34 @@ sprintf(obuf,""); TRPRINT(tri, obuf); } - jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */ - lastx = bri->rects[jdx].xur; - lasty = bri->rects[jdx].yll - tsp->boff; - recenter = 0; /* mostly to quiet a compiler warning, should always be set below */ + jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */ + recenter = 0; /* mostly to quiet a compiler warning, should always be set below */ + /* over all members of the present Line. These are the original text objects which were reassembled. There will be one for TR_TEXT, more than one for TR_LINE */ - for(ptsp = NULL, k=0; kcx[jdx].kids.used; k++){ - if(k){ - ptsp=tsp; /* previous text object in this line */ - fsp = &(fti->fonts[tpi->chunks[kdx].fi_idx]); /* font spec for previous text object */ - } - kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */ - tsp = &tpi->chunks[kdx]; - if(!k){ - switch(csp->type){ /* set up the alignment, if there is one */ + for(k=0; kcx[jdx].kids.used; k++){ + kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi, for this k */ + tsp = &tpi->chunks[kdx]; /* text chunk for this k */ + ldir = tsp->ldir; /* language direction for this k */ + if(!k){ /* first iteration */ + switch(csp->type){ /* set up the alignment, if there is one */ case TR_TEXT: case TR_LINE: /* these should never occur, this section quiets a compiler warning */ break; case TR_PARA_UJ: - recenter=0.0; - break; case TR_PARA_LJ: - recenter=0.0; + if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); } + else { recenter = 0.0; } break; case TR_PARA_CJ: - recenter=(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; + if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; } + else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; } break; case TR_PARA_RJ: - recenter=bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll; + if(ldir == LDIR_RL){ recenter = 0.0; } + else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); } break; } if(!j){ @@ -1685,9 +2099,8 @@ TRPRINT(tri, "fill:#000000;"); TRPRINT(tri, "fill-opacity:1;"); TRPRINT(tri, "stroke:none;"); - cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fname,":"); - fti->fonts[tsp->fi_idx].fname[cutat]='\0'; - sprintf(obuf,"font-family:%s;",fti->fonts[tsp->fi_idx].fname); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec); TRPRINT(tri, obuf); switch(csp->type){ /* set up the alignment, if there is one */ case TR_TEXT: @@ -1695,8 +2108,6 @@ /* these should never occur, this section quiets a compiler warning */ break; case TR_PARA_UJ: - *obuf='\0'; - break; case TR_PARA_LJ: sprintf(obuf,"text-align:start;text-anchor:start;"); break; @@ -1704,42 +2115,30 @@ sprintf(obuf,"text-align:center;text-anchor:middle;"); break; case TR_PARA_RJ: - sprintf(obuf,"text-align:end;text-anchor:end;"); + sprintf(obuf,"text-align:end;text-anchor:end;"); break; } TRPRINT(tri, obuf); TRPRINT(tri, "\"\n"); /* End of style specification */ sprintf(obuf,"transform=\"matrix(%lf,%lf,%lf,%lf,%lf,%lf)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc),1.25*x,1.25*y); TRPRINT(tri, obuf); - sprintf(obuf,"x=\"%lf\" y=\"%lf\"\n>",1.25*(bri->rects[kdx].xll + recenter),1.25*(bri->rects[kdx].yll - tsp->boff)); + tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff)); TRPRINT(tri, obuf); } - sprintf(obuf,"", - 1.25*(bri->rects[kdx].xll + recenter),1.25*(bri->rects[kdx].yll - tsp->boff)); + tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter); + sprintf(obuf,"",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff)); TRPRINT(tri, obuf); } - TRPRINT(tri, "rects[tsp->rt_tidx].xll - lastx); - dy = 1.25*(bri->rects[tsp->rt_tidx].yll - tsp->boff - lasty); - - /* Have to also take into account kerning between the last letter of the preceding rectangle - and the first letter of the current one. Assume font values are from leading retangle's font. */ - if(ptsp && tri->use_kern){ - status = TR_kern_gap(fsp, tsp, ptsp, tri->kern_mode); - if(status){ - dx += (tri->load_flags & FT_LOAD_NO_SCALE ? tsp->fs/32.0: 1.0) * ((double) status)/64.0; - } - } + TRPRINT(tri, "xkern; + dy = 1.25 * tsp->ykern; - /* Sometimes a font substitution was absolutely terrible, for instance, for Arial Narrow on (most) Linux systems, - The resulting advance may be much too large so that it overruns the next text chunk. Since overlapping text on - the same line is almost never encountered, this may be used to detect the bad substitution so that a more appropriate - offset can be used. - Detect this situation as a negative dx < 1/2 a space character's width while |dy| < an entire space width. */ - qsp = 1.25 * 0.25 * fti->fonts[tsp->fi_idx].spcadv; - if((dy <=qsp && dy >= -qsp) && dx < -2*qsp){ dx=0.0; } - if(k==0){ sprintf(obuf,"dx=\"%lf\" dy=\"%lf\" ",0.0, 0.0); } - else { sprintf(obuf,"dx=\"%lf\" dy=\"%lf\" ",dx, dy); } + sprintf(obuf,"dx=\"%lf\" dy=\"%lf\" ",dx, dy); TRPRINT(tri, obuf); sprintf(obuf,"style=\"fill:#%2.2X%2.2X%2.2X;",tsp->color.Red,tsp->color.Green,tsp->color.Blue); TRPRINT(tri, obuf); @@ -1747,38 +2146,44 @@ TRPRINT(tri, obuf); sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal")); TRPRINT(tri, obuf); - switch(tsp->decoration){ - case TXTDECOR_NONE: - case TXTDECOR_STRIKE2: - default: - break; - case TXTDECOR_UNDER: - TRPRINT(tri,"text-decoration:underline;"); - break; - case TXTDECOR_OVER: - TRPRINT(tri,"text-decoration:overline;"); - break; - case TXTDECOR_BLINK: - TRPRINT(tri,"text-decoration:blink;"); - break; - case TXTDECOR_STRIKE1: - TRPRINT(tri,"text-decoration:line-through;"); - break; + if(tsp->decoration & TXTDECOR_TMASK){ + sprintf(obuf,"text-decoration:"); + /* multiple text decoration styles may be set */ + utmp = tsp->decoration & TXTDECOR_TMASK; + if(utmp & TXTDECOR_UNDER ){ strcat(obuf,"underline"); } + if(utmp & TXTDECOR_OVER ){ strcat(obuf,"overline"); } + if(utmp & TXTDECOR_BLINK ){ strcat(obuf,"blink"); } + if(utmp & TXTDECOR_STRIKE){ strcat(obuf,"line-through");} + if(*obuf){ + /* only a single text decoration line type may be set */ + switch(tsp->decoration & TXTDECOR_LMASK){ + case TXTDECOR_SOLID: break; // "solid" is the CSS 3 default, omitting it remains CSS 2 compatible + case TXTDECOR_DOUBLE: strcat(obuf," double"); break; // these are all CSS3 + case TXTDECOR_DOTTED: strcat(obuf," dotted"); break; + case TXTDECOR_DASHED: strcat(obuf," dashed"); break; + case TXTDECOR_WAVY: strcat(obuf," wavy" ); break; + default: break; + } + if((tsp->decoration & TXTDECOR_CLRSET) && memcmp(&(tsp->decColor),&(tsp->color),sizeof(TRCOLORREF))){ + /* CSS 3, CSS 2 implementations may choke on it. If the specified color matches text color omit, for better CSS 2 compatitiblity. */ + sprintf(cbuf," #%2.2X%2.2X%2.2X",tsp->decColor.Red,tsp->decColor.Green,tsp->decColor.Blue); + strcat(obuf,cbuf); + } + } + strcat(obuf,";"); + TRPRINT(tri,obuf); } TRPRINT(tri, "font-variant:normal;"); sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight)); TRPRINT(tri, obuf); sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed")); TRPRINT(tri, obuf); - cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fname,":"); - fti->fonts[tsp->fi_idx].fname[cutat]='\0'; - sprintf(obuf,"font-family:%s;\"",fti->fonts[tsp->fi_idx].fname); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;\"",cutat,fti->fonts[tsp->fi_idx].fontspec); TRPRINT(tri, obuf); TRPRINT(tri, "\n>"); TRPRINT(tri, (char *) tsp->string); TRPRINT(tri, ""); - lastx=bri->rects[tsp->rt_tidx].xur; - lasty=bri->rects[tsp->rt_tidx].yll - tsp->boff; } /* end of k loop */ } /* end of j loop */ TRPRINT(tri,"\n"); @@ -1800,7 +2205,7 @@ \param tri pointer to the TR_INFO structure holding the data, which will also hold the results. */ int TR_layout_analyze(TR_INFO *tri){ - int i,j; + unsigned int i,j,k; int ok; int cxidx; int src_rt; @@ -1813,7 +2218,17 @@ RT_PAD rt_pad_i; RT_PAD rt_pad_j; double ratio; + double qsp,dx,dy; + double spcadv; enum tr_classes type; + TCHUNK_SPECS *tspi; + TCHUNK_SPECS *tspj; + TCHUNK_SPECS *tspRevEnd=NULL; + TCHUNK_SPECS *tspRevStart=NULL; + CX_SPECS *csp; + CHILD_SPECS *kidp; /* used with preceding complex (see below) */ + CHILD_SPECS *kidc; /* used with current complex (see below) */ + int lastldir,ldir,rev; if(!tri)return(-1); if(!tri->cxi)return(-2); @@ -1828,27 +2243,48 @@ cxi->paras = 0; cxi->phase1 = 0; +/* When debugging + ftinfo_dump(fti); +*/ /* Phase 1. Working sequentially, insert text. Initially as TR_TEXT and then try to extend to TR_LINE by checking overlaps. When done the complexes will contain a mix of TR_LINE and TR_TEXT. */ for(i=0; iused; i++){ - memcpy(&bsp,&(bri->rects[tpi->chunks[i].rt_tidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */ + tspi = &(tpi->chunks[i]); + memcpy(&bsp,&(bri->rects[tspi->rt_tidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */ (void) brinfo_insert(bri,&bsp); dst_rt = bri->used-1; (void) cxinfo_insert(cxi, i, dst_rt, TR_TEXT); cxidx = cxi->used-1; - /* for the leading text: pad with no leading and two trailing spaces */ - TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * fti->fonts[tpi->chunks[i].fi_idx].spcadv); + + spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ + /* for the leading text: pad with no leading and two trailing spaces, leading and trailing depend on direction */ + if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); } + else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); } for(j=i+1; jused; j++){ + tspj = &(tpi->chunks[j]); /* Reject font size changes of greater than 50%, these are almost certainly not continuous text. These happen in math formulas, for instance, where a sum or integral is much larger than the other symbols. */ - ratio = (double)(tpi->chunks[j].fs)/(double)(tpi->chunks[i].fs); + ratio = (double)(tspj->fs)/(double)(tspi->fs); if(ratio >2.0 || ratio <0.5)break; - - /* for the trailing text: pad with one leading and trailing spaces (so it should work L->R and R->L */ - TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, fti->fonts[tpi->chunks[j].fi_idx].spcadv, fti->fonts[tpi->chunks[j].fi_idx].spcadv); - src_rt = tpi->chunks[j].rt_tidx; + + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ + /* for the trailing text: pad with one leading and trailing spaces (so it should work L->R and R->L) */ + TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv); + src_rt = tspj->rt_tidx; + + /* Reject direction changes like [1 <- Hebrew][2 -> English], that is where the direction changes AND the + next logical piece of text is "upstream" positionally of its logical predecessor. The meaning of such + a construct is at best ambiguous. The test is only applied with respect to the first text chunk. This sort + of construct may appear when a valid initial construct like [1->English][2<-Hebrew][3->English] is edited + and the leading chunk of text removed. + */ + if(brinfo_upstream(bri, + dst_rt, /* index into bri for dst */ + src_rt, /* index into bri for src */ + tspi->ldir,tspj->ldir))break; + if(!brinfo_overlap(bri, dst_rt, /* index into bri for dst */ src_rt, /* index into bri for src */ @@ -1856,17 +2292,146 @@ (void) cxinfo_append(cxi,j,TR_LINE); (void) brinfo_merge(bri,dst_rt,src_rt); /* for the leading text: pad with two leading and trailing spaces (so it should work L->R and R->L */ + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ TR_rt_pad_set(&rt_pad_i, tri->qe, tri->qe, - tri->qe + 2.0 * fti->fonts[tpi->chunks[j].fi_idx].spcadv, tri->qe + 2.0 * fti->fonts[tpi->chunks[j].fi_idx].spcadv); + tri->qe + 2.0 * spcadv, tri->qe + 2.0 * spcadv); } else { /* either alignment ge*/ break; } } + + /* Bidirectional text will cause complexes to not assemble in one pass. + This happens whenever a change of direction occurs with 2 or more sequential elements in + the opposite direction, + + Let + = LR and - = RL. + + Reading left to right, this happens with +-- or -++. + For instance, the sequence ++-+ ---+ would break into the two complexes shown. + Not until the last element in the second complex is added will the bounding rectangles for the complexes overlap. + + Check for this effect now if there is a preceding complex and the first element of the current complex is + reversed from the last in the preceding. */ + if(cxidx >= 1){ + kidp = &(cxi->cx[cxidx-1].kids); + kidc = &(cxi->cx[cxidx ].kids); + tspi = &(tpi->chunks[ kidp->members[kidp->used - 1] ]); /* here, the last text element in preceding complex */ + tspj = &(tpi->chunks[ kidc->members[0 ] ]); /* here, tge first text element in current complex */ + if(tspi->ldir != tspj->ldir){ + spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; + if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); } + else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); } + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; + TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv); + if(!brinfo_overlap(bri, + cxi->cx[cxidx-1].rt_cidx, /* index into rt for dst cx */ + cxi->cx[cxidx].rt_cidx, /* index into rt for src cx */ + &rt_pad_i,&rt_pad_j)){ + /* Merge the current complex into the preceding one*/ + (void) cxinfo_merge(cxi, cxidx-1, cxidx, TR_LINE); + (void) brinfo_merge(bri,cxi->cx[cxidx-1].rt_cidx,cxi->cx[cxidx].rt_cidx); /* merge the bounding boxes*/ + (void) cxinfo_trim(cxi); + cxi->lines--; /* else the normal line count value is one too high */ + /* remove the current complex */ + } + } + } + + if(cxi->cx[cxidx].type == TR_LINE)cxi->lines++; i=j-1; /* start up after the last merged entry (there may not be any) */ - if(cxi->cx[cxidx].type == TR_LINE)cxi->lines++; } cxi->phase1 = cxi->used; /* total complexes defined in this phase, all TR_LINE or TR_TEXT */ + + /* phase 1.5, calculate kerning. This is as good a place to do it as any. At this point all kern values + are zero. Each of these pieces is strictly unidirectional, but each piece can have a different direction. + The direction of the line is set by the first text element. The ends of runs of elements which are + reversed with respect to the line direction are special, everything else is simple: + Let: + == L->R, - == R->L, $ == end of text, the rules for kerning on B are: + A B others xkern + [+|$] + + [+|$] Bll - Aur + [-|$] - - [-|$] All - Bur (chs) + + - + [-|$] Bll - Aur (chs) + - + - [+|$] All - Bur + + - -...[-=C] [+|$] All - Cur (chs) + - + +...[+=C] [-|$] Cll - Aur + + chs = change sign, because dx is an absolute direction, and direction of text on RTL is in -x. + + Kerning calculations currently seems unstable for R->L if the kerning extends to the end of the line. If + the first and last characters are back in sync there are no issues. When things go south R->L left justified + text is not justified when read in. + */ + + for(i=0; i < cxi->phase1; i++){ /* over all lines */ + csp = &(cxi->cx[i]); + if(csp->kids.used < 2)continue; /* no kerning possible */ + tspi = &tpi->chunks[csp->kids.members[0]]; /* used here as last tsp. no kerning is applied to the first element */ + lastldir = ldir = tspi->ldir; + rev = 0; /* the first ldir defines forward and reverse */ + for(j=1; jkids.used; j++){ + tspj = &tpi->chunks[csp->kids.members[j]]; + ldir = tspj->ldir; + if(ldir != lastldir){ /* direction change */ + rev = !rev; /* reverse direction tracker */ + if(!rev){ /* back in original orientation */ + if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspRevStart->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspRevStart->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) - + (bri->rects[tspRevStart->rt_tidx].yll - tspRevStart->boff); + } + else { /* now in reversed orientation */ + tspRevStart = tspj; /* Save the beginning of this run (length >=1 ) */ + /* scan forward for the last text object in this orientation, include the first */ + for(k=j; k kids.used; k++){ + if(tpi->chunks[csp->kids.members[k]].ldir == ldir){ tspRevEnd = &tpi->chunks[csp->kids.members[k]]; } + else { break; } + } + if(lastldir == LDIR_RL){ tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspRevEnd->rt_tidx].yll - tspRevEnd->boff) - + (bri->rects[ tspi->rt_tidx].yll - tspi->boff ); + } + } + else { + if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) - + (bri->rects[tspi->rt_tidx].yll - tspi->boff); + } + + + /* + Sometimes a font substitution was absolutely terrible, for instance, for Arial Narrow on (most) Linux systems, + The resulting advance (xkern) may be much too large so that it overruns the next text chunk. Since + overlapping text on the same line is almost never encountered, this may be used to detect the bad + substitution so that a more appropriate offset can be used. + Detect this situation as a negative dx < 1/2 a space character's width while |dy| < an entire space width. + The y constraints allow super and subscripts, which overlap in x but are shifted above/below in y. + */ + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; + qsp = 0.25 * spcadv; + dx = tspj->xkern; + dy = tspj->ykern; + if(dy <=qsp && dy >= -qsp){ + if(ldir==LDIR_RL){ + if(dx > 2*qsp)tspj->xkern = 0.0; + } + else { + if(dx < -2*qsp)tspj->xkern = 0.0; + } + } + + /* if x or y kern is less than the quantization error it is probably noise, set it to zero */ + if(fabs(tspj->xkern)qe)tspj->xkern = 0.0; + if(fabs(tspj->ykern)qe)tspj->ykern = 0.0; + + + tspi = tspj; + lastldir = ldir; + } + } + /* Phase 2, try to group sequential lines. There may be "lines" that are still TR_TEXT, as in: @@ -1885,10 +2450,13 @@ for(i=0; i < cxi->phase1; i++){ type = TR_PARA_UJ; /* any paragraph alignment will be acceptable */ - memcpy(&bsp,&(bri->rects[cxi->cx[i].rt_cidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */ + /* Must make a copy as next call may reallocate rects, so if we just passed a pointer to something in the structure + it would vaporize part way through the call. */ + memcpy(&bsp,&(bri->rects[cxi->cx[i].rt_cidx]),sizeof(BRECT_SPECS)); (void) brinfo_insert(bri,&bsp); dst_rt = bri->used-1; (void) cxinfo_insert(cxi, i, dst_rt, type); + cxi->paras++; ok = 1; for(j=i+1; ok && (j < cxi->phase1); j++){ @@ -1910,8 +2478,8 @@ src_rt = cxi->cx[j].rt_cidx; (void) cxinfo_append(cxi, j, type); (void) brinfo_merge(bri, dst_rt, src_rt); - } - break; + } + break; default: return(-6); /* programming error */ } @@ -1920,8 +2488,9 @@ i=j-1; } + /* When debugging - cxinfo_dump(tri); + cxinfo_dump(tri); */ return(cxi->used); @@ -1932,7 +2501,7 @@ #if TEST #define MAXLINE 2048 /* big enough for testing */ -enum OP_TYPES {OPCOM,OPOOPS,OPFONT,OPESC,OPORI,OPXY,OPFS,OPTEXT,OPALN,OPLDIR,OPMUL,OPITA,OPWGT,OPDEC,OPCND,OPCLR,OPBKG,OPBCLR,OPFLAGS,OPEMIT,OPDONE}; +enum OP_TYPES {OPCOM,OPOOPS,OPFONT,OPESC,OPORI,OPXY,OPFS,OPTEXT,OPALN,OPLDIR,OPMUL,OPITA,OPWGT,OPDEC,OPCND,OPBKG,OPCLR,OPDCLR,OPBCLR,OPFLAGS,OPEMIT,OPDONE}; int parseit(char *buffer,char **data){ int pre; @@ -1954,8 +2523,9 @@ if(0==strcmp("WGT", buffer))return(OPWGT ); if(0==strcmp("DEC", buffer))return(OPDEC ); if(0==strcmp("CND", buffer))return(OPCND ); + if(0==strcmp("BKG", buffer))return(OPBKG ); if(0==strcmp("CLR", buffer))return(OPCLR ); - if(0==strcmp("BKG", buffer))return(OPBKG ); + if(0==strcmp("DCLR", buffer))return(OPDCLR ); if(0==strcmp("BCLR",buffer))return(OPBCLR ); if(0==strcmp("FLAG",buffer))return(OPFLAGS); if(0==strcmp("EMIT",buffer))return(OPEMIT); @@ -2066,6 +2636,7 @@ uint32_t utmp32; TRCOLORREF bkcolor; int bkmode; + char *fontspec; infile=malloc(strlen(argv[1])+1); strcpy(infile,argv[1]); @@ -2084,11 +2655,14 @@ printf(" MUL:(float, multiplicative factor to convert FS,XY units to points).\n"); printf(" ITA:(Italics, 0=normal, 100=italics, 110=oblique).\n"); printf(" WGT:(Weight, 0-215: 80=normal, 200=bold, 215=ultrablack, 0=thin)).\n"); - printf(" DEC:(Decorate, 00 none, 01 underline, 02 overline, 04 blink, 08 strike1, 10 strike2. SVG only supports some, and only one at a time.)\n"); + printf(" DEC:(this is a bit field. For color see DCLR\n"); + printf(" style: 000 none, 001 underline,002 overline, 004 blink, 008 strike-through\n"); + printf(" line: 000 solid, 010 double, 020 dotted, 040 dashed, 080 wavy)\n"); printf(" CND:(Condensed 50-200: 100=normal, 50=ultracondensed, 75=condensed, 200=expanded).\n"); + printf(" BKG:(Background color: 0 none, 1 by input fragment, 2 by assembled line, 3 by entire assembly. Use BCLR, THEN BKG) \n"); printf(" CLR:(Text RGB color, as 6 HEX digits, like: FF0000 (red) or 0000FF (blue)) \n"); - printf(" BKG:(Background color: 0 none, 1 by input fragment, 2 by assembled line, 3 by entire assembly. Use BCLR, THEN BKG) \n"); - printf(" BCLR:(Background RGB color, as 6 HEX digits, like: FF0000 (red) or 0000FF (blue)) \n"); + printf(" DCLR:(Decoration color, specify like CLR, except 1000000 or higher disables.)\n"); + printf(" BCLR:(Background RGB color, specify like CLR.) \n"); printf(" FLAG: Special processing options. 1 EMF compatible text alignment.\n"); printf(" EMIT:(Process everything up to this point, then start clean for remaining input).\n"); printf(" DONE:(no more input, process it).\n"); @@ -2096,7 +2670,7 @@ printf("\n"); printf(" The output is a summary of how the pieces are to be assembled into complex text.\n"); printf("\n"); - printf(" egrep pattern: '^LOAD:|^FONT:|^ESC:|^ORI:|^FS:|^XY:|^TEXT:|^ALN:|^LDIR:|^MUL:|^ITA:|^WGT:|^CND:|^BKG:|^BCLR:|^CLR:|^FLAG:|^EMIT:^DONE:'\n"); + printf(" egrep pattern: '^LOAD:|^FONT:|^ESC:|^ORI:|^FS:|^XY:|^TEXT:|^ALN:|^LDIR:|^MUL:|^ITA:|^WGT:|^DEC:|^CND:|^BKG:|^CLR:|^BCLR:|^DCLR:|^FLAG:|^EMIT:^DONE:'\n"); exit(EXIT_FAILURE); } @@ -2155,7 +2729,9 @@ else { tsp.co=0; } - if(trinfo_load_fontname(tri, (uint8_t *) data, &tsp))boom("Font load failed",lineno); + fontspec = TR_construct_fontspec(&tsp, data); + if((tsp.fi_idx = ftinfo_load_fontname(tri->fti, fontspec)) < 0 )boom("Font load failed",lineno); + free(fontspec); break; case OPESC: if(1 != sscanf(data,"%lf",&escapement))boom("Invalid ESC:",lineno); @@ -2223,6 +2799,10 @@ case OPCND: if(1 != sscanf(data,"%d",&tsp.condensed) || tsp.condensed < 50 || tsp.condensed > 200)boom("Invalid CND:",lineno); break; + case OPBKG: + if(1 != sscanf(data,"%d",&bkmode) )boom("Invalid BKG:",lineno); + (void) trinfo_load_bk(tri,bkmode,bkcolor); + break; case OPCLR: if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid CLR:",lineno); tsp.color.Red = (utmp32 >> 16) & 0xFF; @@ -2230,9 +2810,19 @@ tsp.color.Blue = (utmp32 >> 0) & 0xFF; tsp.color.Reserved = 0; break; - case OPBKG: - if(1 != sscanf(data,"%d",&bkmode) )boom("Invalid BKG:",lineno); - (void) trinfo_load_bk(tri,bkmode,bkcolor); + case OPDCLR: + if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid DCLR:",lineno); + if(utmp32 >= 0x1000000){ + tsp.decColor.Red = tsp.decColor.Green = tsp.decColor.Blue = tsp.decColor.Reserved = 0; + tsp.decoration &= ~TXTDECOR_CLRSET; + } + else { + tsp.decColor.Red = (utmp32 >> 16) & 0xFF; + tsp.decColor.Green = (utmp32 >> 8) & 0xFF; + tsp.decColor.Blue = (utmp32 >> 0) & 0xFF; + tsp.decColor.Reserved = 0; + tsp.decoration |= TXTDECOR_CLRSET; + } break; case OPBCLR: if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid BCLR:",lineno); === modified file 'src/extension/internal/text_reassemble.h' --- src/extension/internal/text_reassemble.h 2013-03-19 02:22:04 +0000 +++ src/extension/internal/text_reassemble.h 2013-05-14 23:06:16 +0000 @@ -4,8 +4,8 @@ See text_reassemble.c for notes File: text_reassemble.h -Version: 0.0.7 -Date: 12-FEB-2013 +Version: 0.0.12 +Date: 14-MAY-2013 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2013 David Mathog and California Institute of Technology (Caltech) @@ -56,18 +56,33 @@ /** \defgroup decoration options One of these values may be present in the decoration field. Unused bits may be used by end user code. - SVG output can specify up to STRIKE1. + These values are SVG specific. Other applications could use the text + decoration field for a different set of bits, so long as it provided its own + output function. @{ */ -#define TXTDECOR_NONE 0x00 /**< text is not decorated (default) */ -#define TXTDECOR_UNDER 0x01 /**< underlined */ -#define TXTDECOR_OVER 0x02 /**< overlined */ -#define TXTDECOR_BLINK 0x04 /**< blinking text */ -#define TXTDECOR_STRIKE1 0x08 /**< single strike throug */ -#define TXTDECOR_STRIKE2 0x10 /**< double strike through */ +#define TXTDECOR_NONE 0x000 /**< text is not decorated (default) */ +#define TXTDECOR_UNDER 0x001 /**< underlined */ +#define TXTDECOR_OVER 0x002 /**< overlined */ +#define TXTDECOR_BLINK 0x004 /**< blinking text */ +#define TXTDECOR_STRIKE 0x008 /**< strike through */ +#define TXTDECOR_TMASK 0x00F /**< Mask for selecting bits above */ + +#define TXTDECOR_SOLID 0x000 /**< draw as single solid line */ +#define TXTDECOR_DOUBLE 0x010 /**< draw as double solid line */ +#define TXTDECOR_DOTTED 0x020 /**< draw as single dotted line */ +#define TXTDECOR_DASHED 0x040 /**< draw as single dashed line */ +#define TXTDECOR_WAVY 0x080 /**< draw as single wavy line */ +#define TXTDECOR_LMASK 0x0F0 /**< Mask for selecting these bits */ + +#define TXTDECOR_CLRSET 0x100 /**< decoration has its own color */ + /** @} */ + + + /** \defgroup text alignment types Location of text's {X,Y} coordinate on bounding rectangle. Values are compatible with Fontconfig. @@ -113,15 +128,27 @@ /** @} */ /** + \brief alt font entries. +*/ +typedef struct { + uint32_t fi_idx; /**< index into FT_INFO fonts, for fonts added for missing glyphs */ + uint32_t weight; /**< integer weight for alt fonts, kept sorted into descending order */ +} ALT_SPECS; + +/** \brief Information for a font instance. */ typedef struct { + FcFontSet *fontset; /**< all matching fonts (for fallback on missing glyphs) */ + ALT_SPECS *alts; /**< index into FT_INFO fonts, for fonts added for missing glyphs */ + uint32_t space; /**< alts storage slots allocated */ + uint32_t used; /**< alts storage slots in use */ FT_Face face; /**< font face structures (FT_FACE is a pointer!) */ - uint8_t *file; /**< pointers to font paths to files */ - uint8_t *fname; /**< pointers to font names */ - FcPattern *fpat; /**< must hang onto this or faces operations break */ - double spcadv; /**< advance equal to a space, in points */ - double fsize; /**< face size in points */ + uint8_t *file; /**< pointer to font paths to files */ + uint8_t *fontspec; /**< pointer to a font specification (name:italics, etc.) */ + FcPattern *fpat; /**< current font, must hang onto this or faces operations break */ + double spcadv; /**< advance equal to a space, in points at font's face size */ + double fsize; /**< font's face size in points */ } FNT_SPECS; /** @@ -130,8 +157,8 @@ typedef struct { FT_Library library; /**< Fontconfig handle */ FNT_SPECS *fonts; /**< Array of fontinfo structures */ - int space; /**< storage slots allocated */ - int used; /**< storage slots in use */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ } FT_INFO; typedef struct { @@ -150,6 +177,8 @@ double fs; /**< font size of text */ double x; /**< x coordinate, relative to TR_INFO x,y, in points */ double y; /**< y coordinate, relative to TR_INFO x,y, in points */ + double xkern; /**< x kern relative to preceding text chunk in complex (if any) */ + double ykern; /**< y kern relative to preceding text chunk in complex (if any) */ double boff; /**< Y LL corner - boff finds baseline */ double vadvance; /**< Line spacing typically 1.25 or 1.2, only set on the first text element in a complex */ @@ -159,7 +188,8 @@ int italics; /**< italics, as in FontConfig */ int weight; /**< weight, as in FontConfig */ int condensed; /**< condensed, as in FontConfig */ - int decoration; /**< text decorations, ignored during assembly,used during output */ + int decoration; /**< text decorations, ignored during assembly, used during output */ + TRCOLORREF decColor; /**< text decoration color, ignored during assembly, used during output */ int co; /**< condensed override, if set Font name included narrow */ int rt_tidx; /**< index of rectangle that contains it */ int fi_idx; /**< index of the font it uses */ @@ -171,8 +201,8 @@ */ typedef struct { TCHUNK_SPECS *chunks; /**< text chunks */ - int space; /**< storage slots allocated */ - int used; /**< storage slots in use */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ } TP_INFO; /** @@ -192,8 +222,8 @@ */ typedef struct { BRECT_SPECS *rects; /**< bounding rectangles */ - int space; /**< storage slots allocated */ - int used; /**< storage slots in use */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ } BR_INFO; /** @@ -203,8 +233,8 @@ int *members; /**< array of immediate children (for TR_PARA_* these are indicies for TR_TEXT or TR_LINE complexes also in cxi. For TR_TEXT and TR_LINE these are indices to the actual text in tpi.) */ - int space; /**< storage slots allocated */ - int used; /**< storage slots in use */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ } CHILD_SPECS; /** @@ -222,11 +252,11 @@ */ typedef struct { CX_SPECS *cx; /**< complexes */ - int space; /**< storage slots allocated */ - int used; /**< storage slots in use */ - int phase1; /**< Number of complexes (lines + text fragments) entered in phase 1 */ - int lines; /**< Number of lines in phase 1 */ - int paras; /**< Number of complexes (paras) entered in phase 2 */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ + uint32_t phase1; /**< Number of complexes (lines + text fragments) entered in phase 1 */ + uint32_t lines; /**< Number of lines in phase 1 */ + uint32_t paras; /**< Number of complexes (paras) entered in phase 2 */ } CX_INFO; /** @@ -246,8 +276,8 @@ int use_kern; /**< 1 if kerning is used, 0 if not */ int load_flags; /**< FT_LOAD_NO_SCALE or FT_LOAD_TARGET_NORMAL */ int kern_mode; /**< FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED */ - int outspace; /**< storage in output buffer allocated */ - int outused; /**< storage in output buffer in use */ + uint32_t outspace; /**< storage in output buffer allocated */ + uint32_t outused; /**< storage in output buffer in use */ int usebk; /**< On output write the background color under the text */ TRCOLORREF bkcolor; /**< RGB background color */ } TR_INFO; @@ -279,8 +309,11 @@ /** \endcond */ /* Prototypes */ -int TR_findcasesub(char *string, char *sub); -int TR_getadvance(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax); +int TR_findcasesub(const char *string, const char *sub); +char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname); +char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname); +int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc); +int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax); int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode); int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode); void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right); @@ -295,45 +328,57 @@ int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp); FT_INFO *ftinfo_release(FT_INFO *fti); FT_INFO *ftinfo_clear(FT_INFO *fti); +int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fname); +int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename); +int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec); +void ftinfo_dump(const FT_INFO *fti); + +int fsp_alts_make_insertable(FNT_SPECS *fsp); +int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx); +int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx); int csp_make_insertable(CHILD_SPECS *csp); int csp_insert(CHILD_SPECS *csp, int src); int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src); void csp_release(CHILD_SPECS *csp); +void csp_clear(CHILD_SPECS *csp); CX_INFO *cxinfo_init(void); int cxinfo_make_insertable(CX_INFO *cxi); int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_idx, enum tr_classes type); int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type); int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type); +int cxinfo_trim(CX_INFO *cxi); CX_INFO *cxinfo_release(CX_INFO *cxi); -void cxinfo_dump(TR_INFO *tri); +void cxinfo_dump(const TR_INFO *tri); TP_INFO *tpinfo_init(void); int tpinfo_make_insertable(TP_INFO *tpi); -int tpinfo_insert(TP_INFO *tpi, TCHUNK_SPECS *tsp); +int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp); TP_INFO *tpinfo_release(TP_INFO *tpi); BR_INFO *brinfo_init(void); int brinfo_make_insertable(BR_INFO *bri); -int brinfo_insert(BR_INFO *bri, BRECT_SPECS *element); +int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element); int brinfo_merge(BR_INFO *bri, int dst, int src); enum tr_classes - brinfo_pp_alignment(BR_INFO *bri, int dst, int src, double slop, enum tr_classes type); -int brinfo_overlap(BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src); + brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type); +int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src); BR_INFO *brinfo_release(BR_INFO *bri); TR_INFO *trinfo_init(TR_INFO *tri); TR_INFO *trinfo_release(TR_INFO *tri); TR_INFO *trinfo_release_except_FC(TR_INFO *tri); TR_INFO *trinfo_clear(TR_INFO *tri); -int trinfo_load_fontname(TR_INFO *tri, uint8_t *fontname, TCHUNK_SPECS *tsp); int trinfo_load_qe(TR_INFO *tri, double qe); int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor); int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode); -int trinfo_load_textrec(TR_INFO *tri, TCHUNK_SPECS *tsp, double escapement, int flags); +int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags); int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor); -int trinfo_append_out(TR_INFO *tri, char *src); +int trinfo_append_out(TR_INFO *tri, const char *src); + +int is_mn_unicode(int test); + #ifdef __cplusplus } === modified file 'src/extension/internal/wmf-inout.cpp' --- src/extension/internal/wmf-inout.cpp 2013-03-21 14:07:06 +0000 +++ src/extension/internal/wmf-inout.cpp 2013-05-14 18:22:19 +0000 @@ -1278,8 +1278,10 @@ font.Weight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER : U_FW_NORMAL; d->dc[d->level].style.font_style.value = (font.Italic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL); - d->dc[d->level].style.text_decoration.underline = font.Underline; - d->dc[d->level].style.text_decoration.line_through = font.StrikeOut; + d->dc[d->level].style.text_decoration_line.underline = font.Underline; + d->dc[d->level].style.text_decoration_line.line_through = font.StrikeOut; + d->dc[d->level].style.text_decoration_line.set = true; + d->dc[d->level].style.text_decoration_line.inherit = false; // malformed WMF with empty filename may exist, ignore font change if encountered if(d->dc[d->level].font_name)free(d->dc[d->level].font_name); @@ -1325,13 +1327,13 @@ else if(index == d->dc[d->level].active_font){ d->dc[d->level].active_font = -1; if(d->dc[d->level].font_name){ free(d->dc[d->level].font_name);} - d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants - d->dc[d->level].style.font_size.computed = 16.0; - d->dc[d->level].style.font_weight.value = SP_CSS_FONT_WEIGHT_400; - d->dc[d->level].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL; - d->dc[d->level].style.text_decoration.underline = 0; - d->dc[d->level].style.text_decoration.line_through = 0; - d->dc[d->level].style.baseline_shift.value = 0; + d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants + d->dc[d->level].style.font_size.computed = 16.0; + d->dc[d->level].style.font_weight.value = SP_CSS_FONT_WEIGHT_400; + d->dc[d->level].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL; + d->dc[d->level].style.text_decoration_line.underline = 0; + d->dc[d->level].style.text_decoration_line.line_through = 0; + d->dc[d->level].style.baseline_shift.value = 0; } @@ -2522,6 +2524,7 @@ dbg_str << "\n"; nSize = U_WMREXTTEXTOUT_get(contents, &Dst, &tlen, &Opts, &text, &dx, &rc ); } + uint32_t fOptions = Opts; double x1,y1; int cChars; @@ -2597,11 +2600,10 @@ case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; default: tsp.weight = FC_WEIGHT_NORMAL ; break; } - - // Inkscape cannot display underline or strike-through at present, but enter it into the SVG in any case. - if( d->dc[d->level].style.text_decoration.underline){ tsp.decoration = TXTDECOR_UNDER; } - else if (d->dc[d->level].style.text_decoration.line_through){ tsp.decoration = TXTDECOR_STRIKE1; } - else { tsp.decoration = TXTDECOR_NONE; } + // WMF only supports two types of text decoration + tsp.decoration = TXTDECOR_NONE; + if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; } + if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;} // WMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER : @@ -2610,13 +2612,19 @@ tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE : ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT : ALITOP)); - tsp.ldir = (d->dc[d->level].textAlign & U_TA_RTLREADING ? LDIR_RL : LDIR_LR); // language direction + + // language direction can be encoded two ways, U_TA_RTLREADING is preferred + if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; } + else{ tsp.ldir = LDIR_LR; } + tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet) tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement // There is no world transform, so ori need not be further rotated tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear(). tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points - (void) trinfo_load_fontname(d->tri, (uint8_t *)d->dc[d->level].font_name, &tsp); + char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name); + tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec); + free(fontspec); // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though // as the metrics from fontconfig may not match, or the font may not be present. if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; } @@ -3074,8 +3082,8 @@ d.dc[0].style.font_size.computed = 16.0; d.dc[0].style.font_weight.value = SP_CSS_FONT_WEIGHT_400; d.dc[0].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL; - d.dc[0].style.text_decoration.underline = 0; - d.dc[0].style.text_decoration.line_through = 0; + d.dc[0].style.text_decoration_line.underline = 0; + d.dc[0].style.text_decoration_line.line_through = 0; d.dc[0].style.baseline_shift.value = 0; d.dc[0].textColor = U_RGB(0, 0, 0); // default foreground color (black) d.dc[0].bkColor = U_RGB(255, 255, 255); // default background color (white) === modified file 'src/extension/internal/wmf-print.cpp' --- src/extension/internal/wmf-print.cpp 2013-03-21 14:07:06 +0000 +++ src/extension/internal/wmf-print.cpp 2013-05-07 21:14:16 +0000 @@ -211,7 +211,7 @@ search_long_fflist(fontname, f1, f2, f3); } -void PrintWmf::smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *ndx, float scale){ +void PrintWmf::smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale){ float fdx; int i; int16_t *ladx; @@ -232,6 +232,8 @@ cptr++; // skip 2nd fake terminator sscanf(cptr,"%7f",&fdx); *ky=fdx; + cptr += 7; // advance over ky and its space + sscanf(cptr,"%07d",rtl); } /* convert an 0RGB color to EMF U_COLORREF. @@ -307,6 +309,7 @@ // initialize a few global variables hbrush = hpen = 0; + htextalignment = U_TA_BASELINE | U_TA_LEFT; use_stroke = use_fill = simple_shape = usebk = false; Inkscape::XML::Node *nv = sp_repr_lookup_name (doc->rroot, "sodipodi:namedview"); @@ -383,19 +386,19 @@ g_error("Fatal programming error in PrintWmf::begin at U_WMRSETPOLYFILLMODE"); } - // Text alignment: (Never changes) + // Text alignment: (only changed if RTL text is encountered ) // - (x,y) coordinates received by this filter are those of the point where the text // actually starts, and already takes into account the text object's alignment; // - for this reason, the WMF text alignment must always be TA_BASELINE|TA_LEFT. rec = U_WMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT); if(!rec || wmf_append((PU_METARECORD)rec, wt, U_REC_FREE)){ - g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTALIGN_set"); + g_error("Fatal programming error in PrintWmf::begin at U_WMRSETTEXTALIGN_set"); } htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; rec = U_WMRSETTEXTCOLOR_set(U_RGB(0,0,0)); if(!rec || wmf_append((PU_METARECORD)rec, wt, U_REC_FREE)){ - g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTCOLOR_set"); + g_error("Fatal programming error in PrintWmf::begin at U_WMRSETTEXTCOLOR_set"); } rec = U_WMRSETROP2_set(U_R2_COPYPEN); @@ -1273,8 +1276,8 @@ } } if ( - (style->stroke.noneSet || style->stroke_width.computed == 0.0) || - (style->stroke_dash.n_dash && style->stroke_dash.dash && FixPPTDashLine) || + (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) || + (style->stroke_dash.n_dash && style->stroke_dash.dash && FixPPTDashLine) || !all_closed ){ print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv @@ -1767,10 +1770,21 @@ double ky; // the dx array is smuggled in like: textw1 w2 w3 ...wn, where the widths are floats 7 characters wide, including the space - int ndx; + int ndx, rtl; int16_t *adx; - smuggle_adxky_out(text, &adx, &ky, &ndx, PX2WORLD * std::min(tf.expansionX(),tf.expansionY())); // side effect: free() adx + smuggle_adxky_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(),tf.expansionY())); // side effect: free() adx + uint32_t textalignment; + if(rtl > 0){ textalignment = U_TA_BASELINE | U_TA_LEFT; } + else { textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING; } + if(textalignment != htextalignment){ + htextalignment = textalignment; + rec = U_WMRSETTEXTALIGN_set(textalignment); + if(!rec || wmf_append((PU_METARECORD)rec, wt, U_REC_FREE)){ + g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTALIGN_set"); + } + } + char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char * uint16_t *unicode_text = U_Utf8ToUtf16le( text2, 0, NULL ); free(text2); @@ -1838,8 +1852,8 @@ round(rot), transweight(style->font_weight.computed), (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC), - style->text_decoration.underline, - style->text_decoration.line_through, + style->text_decoration_line.underline, + style->text_decoration_line.line_through, U_DEFAULT_CHARSET, U_OUT_DEFAULT_PRECIS, U_CLIP_DEFAULT_PRECIS, @@ -1922,7 +1936,12 @@ // This is currently being smuggled in from caller as part of text, works // MUCH better than the fallback hack below // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up - rec = U_WMREXTTEXTOUT_set((U_POINT16) {xpos, ypos}, ndx, U_ETO_NONE, latin1_text, adx, U_RCL16_DEF); + if(rtl>0){ + rec = U_WMREXTTEXTOUT_set((U_POINT16) {xpos, ypos}, ndx, U_ETO_NONE, latin1_text, adx, U_RCL16_DEF); + } + else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case + rec = U_WMREXTTEXTOUT_set((U_POINT16) {xpos, ypos}, ndx, U_ETO_RTLREADING, latin1_text, adx, U_RCL16_DEF); + } free(latin1_text); free(adx); if(!rec || wmf_append((PU_METARECORD)rec, wt, U_REC_FREE)){ === modified file 'src/extension/internal/wmf-print.h' --- src/extension/internal/wmf-print.h 2013-03-19 02:22:04 +0000 +++ src/extension/internal/wmf-print.h 2013-05-07 15:54:58 +0000 @@ -38,6 +38,7 @@ double _height; U_RECTL rc; + uint32_t htextalignment; uint32_t hbrush, hpen, hpenOld, hbrush_null, hpen_null; uint32_t hmiterlimit; // used to minimize redundant records that set this uint32_t hpolyfillmode; // used to minimize redundant records that set this @@ -94,7 +95,7 @@ static void read_system_fflist(void); //this is not called by any other source files static void search_long_fflist(const char *fontname, double *f1, double *f2, double *f3); static void search_short_fflist(const char *fontname, double *f1, double *f2, double *f3); - static void smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *ndx, float scale); + static void smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale); U_COLORREF gethexcolor(uint32_t color); uint32_t transweight(const unsigned int inkweight); === modified file 'src/libnrtype/FontInstance.cpp' --- src/libnrtype/FontInstance.cpp 2013-02-02 07:44:07 +0000 +++ src/libnrtype/FontInstance.cpp 2013-04-30 15:52:59 +0000 @@ -675,6 +675,41 @@ return true; } +bool font_instance::FontDecoration( + double &underline_position, double &underline_thickness, + double &linethrough_position, double &linethrough_thickness +){ + if ( pFont == NULL ) { + return false; + } + InitTheFace(); + if ( theFace == NULL ) { + return false; + } +#ifdef USE_PANGO_WIN32 + OUTLINETEXTMETRIC otm; + if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) { + return false; + } + double scale=1.0/daddy->fontSize; + underline_position = fabs(otm.otmUnderscorePosition *scale); + underline_thickness = fabs(otm.otmUnderscoreSize *scale); + linethrough_position = fabs(otm.otmStrikeoutPosition *scale); + linethrough_thickness = fabs(otm.otmStrikeoutSize *scale); +#else + if ( theFace->units_per_EM == 0 ) { + return false; // bitmap font + } + underline_position = fabs(((double)theFace->underline_position )/((double)theFace->units_per_EM)); + underline_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM)); + // there is no specific linethrough information, mock it up from other font fields + linethrough_position = fabs(((double)theFace->ascender / 3.0 )/((double)theFace->units_per_EM)); + linethrough_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM)); +#endif + return true; +} + + bool font_instance::FontSlope(double &run, double &rise) { run = 0.0; === modified file 'src/libnrtype/Layout-TNG-Compute.cpp' --- src/libnrtype/Layout-TNG-Compute.cpp 2010-11-17 02:12:56 +0000 +++ src/libnrtype/Layout-TNG-Compute.cpp 2013-05-15 19:26:19 +0000 @@ -120,8 +120,8 @@ BrokenSpan. */ struct UnbrokenSpan { PangoGlyphString *glyph_string; - int pango_item_index; /// index into _para.pango_items, or -1 if this is style only - unsigned input_index; /// index into Layout::_input_stream + int pango_item_index; /// index into _para.pango_items, or -1 if this is style only + unsigned input_index; /// index into Layout::_input_stream Glib::ustring::const_iterator input_stream_first_character; double font_size; LineHeight line_height; /// This is not the CSS line-height attribute! @@ -213,6 +213,43 @@ /* *********************************************************************************************************/ // Per-line functions +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void dumpPangoItemsOut(ParagraphInfo *para){ + std::cout << "Pango items: " << para->pango_items.size() << std::endl; + for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){ + std::cout + << "idx: " << pidx + << " offset: " + << para->pango_items[pidx].item->offset + << " length: " + << para->pango_items[pidx].item->length + << std::endl; + } +} + +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void dumpUnbrokenSpans(ParagraphInfo *para){ + std::cout << "Unbroken Spans: " << para->unbroken_spans.size() << std::endl; + for(unsigned uidx = 0 ; uidx < para->unbroken_spans.size(); uidx++){ + std::cout + << "idx: " << uidx + << " pango_item_index: " << para->unbroken_spans[uidx].pango_item_index + << " input_index: " << para->unbroken_spans[uidx].input_index + << " char_index_in_para: " << para->unbroken_spans[uidx].char_index_in_para + << " text_bytes: " << para->unbroken_spans[uidx].text_bytes + << std::endl; + } +} + + bool _goToNextWrapShape(); @@ -246,8 +283,10 @@ { span->setZero(); - if (span->start.iter_span->dx._set && span->start.char_byte == 0) - span->width += span->start.iter_span->dx.computed; + if (span->start.iter_span->dx._set && span->start.char_byte == 0){ + if(para.direction == RIGHT_TO_LEFT){ span->width -= span->start.iter_span->dx.computed; } + else { span->width += span->start.iter_span->dx.computed; } + } if (span->start.iter_span->pango_item_index == -1) { // if this is a style-only span there's no text in it @@ -510,15 +549,19 @@ x = 0.0; } else { direction_sign = -1.0; - if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()) + if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){ x = it_chunk->scanrun_width; - else + } + else { x = it_chunk->text_width; + } } for (std::vector::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) { // begin adding spans to the list UnbrokenSpan const &unbroken_span = *it_span->start.iter_span; + double x_in_span_last = 0.0; // set at the END when a new cluster starts + double x_in_span = 0.0; // set from the preceding at the START when a new cluster starts. if (it_span->start.char_byte == 0) { // Start of an unbroken span, we might have dx, dy or rotate still to process @@ -535,7 +578,6 @@ } Layout::Span new_span; - double x_in_span = 0.0; new_span.in_chunk = _flow._chunks.size() - 1; new_span.line_height = unbroken_span.line_height; @@ -581,14 +623,19 @@ InputStreamTextSource const *text_source = static_cast(_flow._input_stream[unbroken_span.input_index]); Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ; unsigned char_index_in_unbroken_span = it_span->start.char_index; - unsigned cluster_start_char_index = _flow._characters.size(); - double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier); + double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier); + int log_cluster_size_glyphs = 0; // Number of glyphs in this log_cluster + int log_cluster_size_chars = 0; // Number of characters in this log_cluster + unsigned end_byte = 0; for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) { - unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); - if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) - cluster_start_char_index = _flow._characters.size(); - + unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + int newcluster = 0; + if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start){ + newcluster = 1; + x_in_span = x_in_span_last; + } + if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes && *iter_source_text == UNICODE_SOFT_HYPHEN && glyph_index + 1 != it_span->end_glyph_index) { @@ -611,7 +658,7 @@ // create the Layout::Glyph Layout::Glyph new_glyph; new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph; - new_glyph.in_character = cluster_start_char_index; + new_glyph.in_character = _flow._characters.size(); new_glyph.rotation = glyph_rotate; /* put something like this back in when we do glyph-rotation-horizontal/vertical @@ -652,28 +699,42 @@ } new_glyph.x -= cluster_width; } - _flow._glyphs.push_back(new_glyph); + _flow._glyphs.push_back(new_glyph); // create the Layout::Character(s) double advance_width = new_glyph.width; - unsigned end_byte; - if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1) - end_byte = it_span->start.iter_span->text_bytes; - else { - // output chars for the whole cluster that is commenced by this glyph - if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) { - int next_cluster_glyph_index = glyph_index + 1; - while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs - && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start) - next_cluster_glyph_index++; - if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs) - end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index]; - else - end_byte = it_span->start.iter_span->text_bytes; - } else - end_byte = char_byte; // don't output any chars if we're not at the start of a cluster + if (newcluster){ + // find where the text ends for this log_cluster + end_byte = it_span->start.iter_span->text_bytes; // Upper limit + for(unsigned next_glyph_index = glyph_index+1; next_glyph_index < it_span->end_glyph_index; next_glyph_index++){ + if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){ + end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index]; + break; + } + } + // Figure out how many glyphs and characters are in the log_cluster. + log_cluster_size_glyphs = 0; + log_cluster_size_chars = 0; + for(; log_cluster_size_glyphs + glyph_index < it_span->end_glyph_index; log_cluster_size_glyphs++){ + if(unbroken_span.glyph_string->log_clusters[glyph_index ] != + unbroken_span.glyph_string->log_clusters[glyph_index + log_cluster_size_glyphs])break; + } + Glib::ustring::const_iterator lclist = iter_source_text; + unsigned lcb = char_byte; + while(lcb < end_byte){ + log_cluster_size_chars++; + lclist++; + lcb = lclist.base() - unbroken_span.input_stream_first_character.base(); + } } while (char_byte < end_byte) { + /* Hack to survive ligatures: in log_cluster keep the number of available chars >= number of glyphs remaining. + When there are no ligatures these two sizes are always the same. + */ + if(log_cluster_size_chars < log_cluster_size_glyphs){ + log_cluster_size_glyphs--; + break; + } Layout::Character new_character; new_character.in_span = _flow._spans.size(); new_character.x = x_in_span; @@ -687,23 +748,27 @@ iter_source_text++; char_index_in_unbroken_span++; char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + log_cluster_size_chars--; } - advance_width *= direction_sign; - if (new_span.direction != para.direction) { - counter_directional_width_remaining -= advance_width; - x -= advance_width; - x_in_span -= advance_width; - } else { - x += advance_width; - x_in_span += advance_width; + if (newcluster){ + advance_width *= direction_sign; + if (new_span.direction != para.direction) { + counter_directional_width_remaining -= advance_width; + x -= advance_width; + x_in_span_last -= advance_width; + } else { + x += advance_width; + x_in_span_last += advance_width; + } } + newcluster = 0; } } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) { x += static_cast(_flow._input_stream[unbroken_span.input_index])->width; } - new_span.x_end = new_span.x_start + x_in_span; + new_span.x_end = new_span.x_start + x_in_span_last; _flow._spans.push_back(new_span); previous_direction = new_span.direction; } @@ -879,8 +944,9 @@ * * Input: para.first_input_index. * Output: para.direction, para.pango_items, para.char_attributes. + * Returns: the number of spans created by pango_itemize */ -void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const +void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const { Glib::ustring para_text; PangoAttrList *attributes_list; @@ -974,8 +1040,9 @@ line_height->setZero(); *line_height_multiplier = 1.0; } - else + else { font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading); + } *line_height *= font_size; // yet another borked SPStyle member that we're going to have to fix ourselves @@ -1008,6 +1075,11 @@ *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total(); } +bool compareGlyphWidth(const PangoGlyphInfo &a, const PangoGlyphInfo &b) +{ + return (a.geometry.width > b.geometry.width); +} + /** * Split the paragraph into spans. Also call pango_shape() on them. @@ -1034,13 +1106,13 @@ break; // stop at the end of the paragraph else if (control_code->code == ARBITRARY_GAP) { UnbrokenSpan new_span; - new_span.pango_item_index = -1; - new_span.input_index = input_index; - new_span.line_height.ascent = control_code->ascent; + new_span.pango_item_index = -1; + new_span.input_index = input_index; + new_span.line_height.ascent = control_code->ascent; new_span.line_height.descent = control_code->descent; new_span.line_height.leading = 0.0; - new_span.text_bytes = 0; - new_span.char_index_in_para = char_index_in_para; + new_span.text_bytes = 0; + new_span.char_index_in_para = char_index_in_para; para->unbroken_spans.push_back(new_span); TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1)); } @@ -1123,6 +1195,13 @@ g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() ); g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast(new_span.text_bytes)) == NULL ); + /* Notes as of 4/29/13. Pango_shape is not generating English language ligatures, but it is generating + them for Hebrew (and probably other similar languages). In the case observed 3 unicode characters (a base + and 2 Mark, nonspacings) are merged into two glyphs (the base + first Mn, the 2nd Mn). All of these map + from glyph to first character of the log_cluster range. This destroys the 1:1 correspondence between + characters and glyphs. A big chunk of the conditional code which immediately follows this call + is there to clean up the resulting mess. + */ pango_shape(text_source->text->data() + span_start_byte_in_source, new_span.text_bytes, ¶->pango_items[pango_item_index].item->analysis, @@ -1134,24 +1213,55 @@ // let's reverse the glyphstring on a cluster-by-cluster basis const unsigned nglyphs = new_span.glyph_string->num_glyphs; std::vector infos(nglyphs); - std::vector clusters(nglyphs); - unsigned i, cluster_start = 0; + std::vector clusters(nglyphs); + unsigned i, j; + for (i = 0 ; i < nglyphs ; i++)new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0; + for (i = 0 ; i < nglyphs ; i++) { + j=i; + while( (j < nglyphs-1) && + (new_span.glyph_string->log_clusters[j+1] == new_span.glyph_string->log_clusters[i]) + )j++; + /* + CAREFUL, within a log_cluster the order of glyphs may not map 1:1, or + even in the same order, to the original unicode characters!!! Among + other things, diacritical mark glyphs can end up in front of the base + character. That makes determining kerning, even approximately, difficult + later on. To resolve this somewhat sort the glyphs with the same + log_cluster into descending order by width. In theory there should be 1 + that is nonzero, and N that are zero. The order of the zero width ones + does not matter. Sort the glyphs before copying. If ligatures other than with + Mark, nonspacing are ever implemented in Pango this will screw up, for instance + changing "fi" to "if". + */ + if(j - i){ + std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j+1]), compareGlyphWidth); + } - for (i = 0 ; i < nglyphs ; ++i) { - if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) { - if (i != cluster_start) { - std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i); - std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i); - } - cluster_start = i; - } - } - if (i != cluster_start) { - std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i); - std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i); + new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1; + std::copy(&new_span.glyph_string->glyphs[ i], &new_span.glyph_string->glyphs[ j+1], infos.end() - j -1); + std::copy(&new_span.glyph_string->log_clusters[i], &new_span.glyph_string->log_clusters[j+1], clusters.end() - j -1); + i = j; } std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs); std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters); + /* glyphs[].x_offset values are probably out of order within any log_clusters, apparently harmless */ + } + else { // ltr sections are in order but glyphs in a log_cluster following a ligature may not be. Sort, but no block swapping. + const unsigned nglyphs = new_span.glyph_string->num_glyphs; + unsigned i, j; + for (i = 0 ; i < nglyphs ; i++)new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0; + for (i = 0 ; i < nglyphs ; i++) { + j=i; + while( (j < nglyphs-1) && + (new_span.glyph_string->log_clusters[j+1] == new_span.glyph_string->log_clusters[i]) + )j++; + if(j - i){ + std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j+1]), compareGlyphWidth); + } + new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1; + i = j; + } + /* glyphs[].x_offset values may be out of order within any log_clusters, apparently harmless */ } new_span.pango_item_index = pango_item_index; _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier); @@ -1469,7 +1579,7 @@ if (_scanline_maker == NULL) break; // we're trying to flow past the last wrap shape - _buildPangoItemizationForPara(¶); + _buildPangoItemizationForPara(¶); unsigned para_end_input_index = _buildSpansForPara(¶); if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE) === modified file 'src/libnrtype/Layout-TNG-Output.cpp' --- src/libnrtype/Layout-TNG-Output.cpp 2013-03-15 17:24:54 +0000 +++ src/libnrtype/Layout-TNG-Output.cpp 2013-05-20 21:46:46 +0000 @@ -39,35 +39,44 @@ /* dx array (character widths) and - ky (vertical kerning for entire span) + ky (vertical kerning for entire span) + rtl (+1 for LTR, -1 RTL) are smuggled through to the EMF (ignored by others) as: textN w1 w2 w3 ...wNy1 y2 y3 .. yN - where the widths and y kern values are floats 7 characters wide, including the space + The ndx, widths, y kern, and rtl are all 7 characters wide. ndx and rtl are ints, the widths and ky are + formatted as ' 6f'. */ -char *smuggle_adxky_in(const char *string, int ndx, float *adx, float ky){ - int slen=strlen(string); +char *smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl){ + int slen = strlen(string); /* holds: string fake terminator (one \0) - Number of widths (ndxy) - series of widths (ndxy entries) + Number of widths (ndx) + series of widths (ndx entries) fake terminator (one \0) y kern value (one float) + rtl value (one float) real terminator (two \0) */ - int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 2; + int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 7 + 2; newsize = 8*((7 + newsize)/8); // suppress valgrind messages if it is a multiple of 8 bytes??? - char *smuggle=(char *)calloc(newsize,1); // initialize all bytes, inluding terminators - strcpy(smuggle,string); // text to pass + char *smuggle=(char *)malloc(newsize); + strcpy(smuggle,string); // text to pass, includes the first fake terminator char *cptr = smuggle + slen + 1; // immediately after the first fake terminator sprintf(cptr,"%07d",ndx); // number of widths to pass - cptr+=7; + cptr+=7; // advance over ndx for(int i=0; iType() != TEXT_SOURCE) continue; InputStreamTextSource const *text_source = static_cast(_input_stream[_spans[span_index].in_input_stream_item]); + text_source->style->text_decoration_data.tspan_width = _spans[span_index].width(); + text_source->style->text_decoration_data.ascender = _spans[span_index].line_height.getAscent(); + text_source->style->text_decoration_data.descender = _spans[span_index].line_height.getDescent(); + text_source->style->text_decoration_data.line_gap = _spans[span_index].line_height.getLeading(); + if(!span_index || + (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index-1].in_chunk].in_line)){ + text_source->style->text_decoration_data.tspan_line_start = true; + } + else { + text_source->style->text_decoration_data.tspan_line_start = false; + } + if((span_index == _spans.size() -1) || + (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index+1].in_chunk].in_line)){ + text_source->style->text_decoration_data.tspan_line_end = true; + } + else { + text_source->style->text_decoration_data.tspan_line_end = false; + } + if(_spans[span_index].font){ + double underline_thickness, underline_position, line_through_thickness,line_through_position; + _spans[span_index].font->FontDecoration(underline_position, underline_thickness, line_through_position, line_through_thickness); + text_source->style->text_decoration_data.underline_thickness = underline_thickness; + text_source->style->text_decoration_data.underline_position = underline_position; + text_source->style->text_decoration_data.line_through_thickness = line_through_thickness; + text_source->style->text_decoration_data.line_through_position = line_through_position; + } + else { // can this case ever occur? + text_source->style->text_decoration_data.underline_thickness = + text_source->style->text_decoration_data.underline_position = + text_source->style->text_decoration_data.line_through_thickness = + text_source->style->text_decoration_data.line_through_position = 0.0; + } + DrawingText *nr_text = new DrawingText(in_arena->drawing()); - nr_text->setStyle(text_source->style); + bool first_line_glyph = true; while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) { if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) { Geom::Affine glyph_matrix; _getGlyphTransformMatrix(glyph_index, &glyph_matrix); - nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix); + if(first_line_glyph && text_source->style->text_decoration_data.tspan_line_start){ + first_line_glyph = false; + phase0 = glyph_matrix.translation()[Geom::X]; + } + // save the starting coordinates for the line - these are needed for figuring out dot/dash/wave phase + (void) nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix, + _glyphs[glyph_index].width, + _spans[span_index].line_height.getAscent(), + _spans[span_index].line_height.getDescent(), + glyph_matrix.translation()[Geom::X] - phase0 + ); } glyph_index++; } + nr_text->setStyle(text_source->style); nr_text->setItemBounds(paintbox); in_arena->prependChild(nr_text); } @@ -158,36 +212,33 @@ return bbox; } +/* This version is much simpler than the old one +*/ void Layout::print(SPPrintContext *ctx, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox, Geom::Affine const &ctm) const { -int doUTN=0; -int lasttarget=0; -int newtarget=0; +bool text_to_path = ctx->module->textToPath(); +int oldtarget = 0; +int newtarget = 0; #define MAX_DX 2048 -float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this. -float ky; // For smuggling y kern value for span -int ndx=0; +float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this. +float ky; // For smuggling y kern value for span +int ndx = 0; +double rtl = 1.0; // 1 L->R, -1 R->L, constant across a span. 1.0 for t->b b->t??? +Geom::Affine glyph_matrix; + +//Glib::ustring debugme = Layout::dumpAsText(); +//std:: cout << "DEBUG Layout::print is \n" << debugme.c_str() << std::endl; //DEBUG if (_input_stream.empty()) return; - - Direction block_progression = _blockProgression(); - bool text_to_path = ctx->module->textToPath(); - doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true - for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) { - if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) { - // invisible glyphs - unsigned same_character = _glyphs[glyph_index].in_character; - while (_glyphs[glyph_index].in_character == same_character) - glyph_index++; - continue; - } - Geom::Affine glyph_matrix; - Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span]; - InputStreamTextSource const *text_source = static_cast(_input_stream[span.in_input_stream_item]); - if (text_to_path || _path_fitted) { + if (!_glyphs.size()) return; // yes, this can happen. + if (text_to_path || _path_fitted) { + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) { + if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1)continue; //invisible glyphs + Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span]; Geom::PathVector const * pv = span.font->PathVector(_glyphs[glyph_index].glyph); + InputStreamTextSource const *text_source = static_cast(_input_stream[span.in_input_stream_item]); if (pv) { _getGlyphTransformMatrix(glyph_index, &glyph_matrix); Geom::PathVector temp_pv = (*pv) * glyph_matrix; @@ -196,9 +247,28 @@ if (!text_source->style->stroke.isNone()) sp_print_stroke(ctx, temp_pv, ctm, text_source->style, pbox, dbox, bbox); } - glyph_index++; - } else { - Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix + } + } + else { + /* index by characters, referencing glyphs and spans only as needed */ + double char_x; + int doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true + Direction block_progression = _blockProgression(); + + for (unsigned char_index = 0 ; char_index < _characters.size() ; ) { + Glib::ustring text_string; // accumulate text for record in this + Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix + int glyph_index = _characters[char_index].in_glyph; + if(glyph_index == -1){ // if the character maps to an invisible glyph we cannot know its geometry, so skip it and move on + char_index++; + continue; + } + ky = _glyphs[glyph_index].y; // same value for all positions in a span + unsigned span_index = _characters[char_index].in_span; + Span const &span = _spans[span_index]; + char_x = 0.0; + Glib::ustring::const_iterator text_iter = span.input_stream_first_character; + InputStreamTextSource const *text_source = static_cast(_input_stream[span.in_input_stream_item]); glyph_matrix = Geom::Scale(1.0, -1.0) * (Geom::Affine)Geom::Rotate(_glyphs[glyph_index].rotation); if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) { glyph_matrix[4] = span.line(this).baseline_y + span.baseline_shift; @@ -208,78 +278,113 @@ glyph_matrix[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x; glyph_matrix[5] = span.line(this).baseline_y + span.baseline_shift; } - Glib::ustring::const_iterator span_iter = span.input_stream_first_character; - unsigned char_index = _glyphs[glyph_index].in_character; - unsigned original_span = _characters[char_index].in_span; - while (char_index && _characters[char_index - 1].in_span == original_span) { - char_index--; - span_iter++; - } - - // try to output as many characters as possible in one go by detecting kerning and stopping when we encounter it - // also break spans at changes in Unicode->nonunicode translations, so that each span - // sent down from here is translated the same way. The translation happens much later - // in the emf-print code. - // Note that the incoming stream has a predefined notion of what is in each "span" and it is not - // entirely clear why. For instance, the string "%%% text %%%%", where % is the Unicode "Sagittarius" - // character has 3 spans, with the first two ending on the spaces. Yet in the XML there is only one tspan. - // Consequently when the Unicode->NonUnicode detection is on the first space will go out by itself, - // because it is at the end of a span, whereas the second space goes with the "text". - - Glib::ustring span_string; - double char_x = _characters[_glyphs[glyph_index].in_character].x; - unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span; - if(doUTN)newtarget=lasttarget=SingleUnicodeToNon(*span_iter); - - do { -/* -std::cout << "glyph info at:" << glyph_index -<< " glyphNo:" << _glyphs[glyph_index].glyph -<< " in_character:" << _glyphs[glyph_index].in_character -<< " x:" << _glyphs[glyph_index].x -<< " y:" << _glyphs[glyph_index].y -<< " rotation:" << _glyphs[glyph_index].rotation -<< " width:" << _glyphs[glyph_index].width -<< std::endl; -*/ - span_string += *span_iter; - span_iter++; - if(doUTN)newtarget=SingleUnicodeToNon(*span_iter); - - unsigned same_character = _glyphs[glyph_index].in_character; - ky = _glyphs[glyph_index].y; // same value for all positions in a span - while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) { - char_x += _glyphs[glyph_index].width; - if(ndx < MAX_DX){ - hold_dx[ndx++] = _glyphs[glyph_index].width; - } - else { // silently truncate any text line silly enough to be longer than MAX_DX - break; - } - glyph_index++; - } - } while (glyph_index < _glyphs.size() - && _path_fitted == NULL - && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index - && fabs(char_x - _characters[_glyphs[glyph_index].in_character].x) < 1e-4 - && (doUTN ? (lasttarget==newtarget ? 1 : 0) : 1 ) - ); + switch(span.direction){ + case Layout::TOP_TO_BOTTOM: + case Layout::BOTTOM_TO_TOP: + case Layout::LEFT_TO_RIGHT: rtl = 1.0; break; + case Layout::RIGHT_TO_LEFT: rtl = -1.0; break; + } + if(doUTN)oldtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation + + // accumulate a record to write + + unsigned lc_index = char_index; + unsigned hold_iisi = _spans[span_index].in_input_stream_item; + while(1){ + glyph_index = _characters[lc_index].in_glyph; + if(glyph_index == -1){ // end of a line within a paragraph, for instance + lc_index++; + break; + } + + // always append if here + text_string += *text_iter; + + // figure out char widths, used by EMF, not currently used elsewhere + double cwidth; + if(lc_index == _glyphs[glyph_index].in_character){ // Glyph width is used only for the first character, these may be 0 + cwidth = rtl * _glyphs[glyph_index].width; // width might be zero + } + else { + cwidth = 0; + } + char_x += cwidth; +/* +std:: cout << "DEBUG Layout::print in while " +<< " char_index " << char_index +<< " lc_index " << lc_index +<< " character " << std::hex << (int) *text_iter << std::dec +<< " glyph_index " << glyph_index +<< " glyph_xy " << _glyphs[glyph_index].x << " , " << _glyphs[glyph_index].y +<< " span_index " << span_index +<< " hold_iisi " << hold_iisi +<< std::endl; //DEBUG +*/ + if(ndx < MAX_DX){ + hold_dx[ndx++] = fabs(cwidth); + } + else { // silently truncate any text line silly enough to be longer than MAX_DX + lc_index = _characters.size(); + break; + } + + + // conditions that prevent this character from joining the record + lc_index++; + if(lc_index >= _characters.size()) break; // nothing more to process, so it must be the end of the record + text_iter++; + if(doUTN)newtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation + if(newtarget != oldtarget)break; // change in unicode to nonunicode translation status + // MUST exit on any major span change, but not on some little events, like a font substitution event irrelvant for the file save + unsigned next_span_index = _characters[lc_index].in_span; + if(span_index != next_span_index){ + /* on major changes break out of loop. + 1st case usually indicates an entire input line has been processed (out of several in a paragraph) + 2nd case usually indicates that a format change within a line (font/size/color/etc) is present. + */ +/* +std:: cout << "DEBUG Layout::print in while --- " +<< " char_index " << char_index +<< " lc_index " << lc_index +<< " cwidth " << cwidth +<< " _char.x (next) " << (lc_index < _characters.size() ? _characters[lc_index].x : -1) +<< " char_x (end this)" << char_x +<< " diff " << fabs(char_x - _characters[lc_index].x) +<< " oldy " << ky +<< " nexty " << _glyphs[_characters[lc_index].in_glyph].y +<< std::endl; //DEBUG +*/ + if(hold_iisi != _spans[next_span_index].in_input_stream_item)break; // major change, font, size, color, etc, must exit + if(fabs(char_x - _spans[next_span_index].x_start) >= 1e-4)break; // xkerning change + if(ky != _glyphs[_characters[lc_index].in_glyph].y)break; // ykerning change + /* + None of the above? Then this is a minor "pangito", update span_index and keep going. + The font used by the display may have failed over, but print does not care and can continue to use + whatever was specified in the XML. + */ + span_index = next_span_index; + text_iter = _spans[span_index].input_stream_first_character; + } + + } + // write it sp_print_bind(ctx, glyph_matrix, 1.0); - // the dx array is smuggled through to the EMF (ignored by others) as: + // the dx array is smuggled through to the EMF driver (ignored by others) as: // textw1 w2 w3 ...wn // where the widths are floats 7 characters wide, including the space - char *smuggle_string=smuggle_adxky_in(span_string.c_str(),ndx, &hold_dx[0], ky); -// sp_print_text(ctx, span_string.c_str(), g_pos, text_source->style); + char *smuggle_string=smuggle_adxkyrtl_in(text_string.c_str(),ndx, &hold_dx[0], ky, rtl); sp_print_text(ctx, smuggle_string, g_pos, text_source->style); free(smuggle_string); sp_print_release(ctx); ndx=0; + char_index = lc_index; } } } + #ifdef HAVE_CAIRO_PDF void Layout::showGlyphs(CairoRenderContext *ctx) const { @@ -432,10 +537,38 @@ Glib::ustring Layout::dumpAsText() const { Glib::ustring result; + char line[256]; + + Glib::ustring::const_iterator icc; + + snprintf(line, sizeof(line), "spans %d\n", _spans.size()); + result += line; + snprintf(line, sizeof(line), "chars %d\n", _characters.size()); + result += line; + snprintf(line, sizeof(line), "glyphs %d\n", _glyphs.size()); + result += line; + unsigned lastspan=5000; + if(_characters.size() > 1){ + for(unsigned j = 0; j < _characters.size() ; j++){ + if(lastspan != _characters[j].in_span){ + lastspan = _characters[j].in_span; + icc = _spans[lastspan].input_stream_first_character; + } + snprintf(line, sizeof(line), "char %4d: '%c' 0x%4.4x x=%8.4f glyph=%3d span=%3d\n", j, *icc, *icc, _characters[j].x, _characters[j].in_glyph, _characters[j].in_span); + result += line; + icc++; + } + } + if(_glyphs.size()){ + for(unsigned j = 0; j < _glyphs.size() ; j++){ + snprintf(line, sizeof(line), "glyph %4d: %4d (%8.4f,%8.4f) rot=%8.4f cx=%8.4f char=%4d\n", + j, _glyphs[j].glyph, _glyphs[j].x, _glyphs[j].y, _glyphs[j].rotation, _glyphs[j].width, _glyphs[j].in_character); + result += line; + } + } for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { - char line[256]; - snprintf(line, sizeof(line), "==== span %d\n", span_index); + snprintf(line, sizeof(line), "==== span %d \n", span_index); result += line; snprintf(line, sizeof(line), " in para %d (direction=%s)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph, direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction)); @@ -469,8 +602,8 @@ if (_characters[char_index].in_span != span_index) continue; if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) { snprintf(line, sizeof(line), " %d: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph); - } else { - snprintf(line, sizeof(line), " %d: '%c' x=%f flags=%03x glyph=%d\n", char_index, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph); + } else { // some text has empty tspans, iter_char cannot be dereferenced + snprintf(line, sizeof(line), " %d: '%c' 0x%4.4x x=%f flags=%03x glyph=%d\n", char_index, *iter_char, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph); iter_char++; } result += line; === modified file 'src/libnrtype/Layout-TNG.h' --- src/libnrtype/Layout-TNG.h 2013-03-14 23:24:17 +0000 +++ src/libnrtype/Layout-TNG.h 2013-05-06 17:03:31 +0000 @@ -568,6 +568,9 @@ inline void setZero() {ascent = descent = leading = 0.0;} inline LineHeight& operator*=(double x) {ascent *= x; descent *= x; leading *= x; return *this;} void max(LineHeight const &other); /// makes this object contain the largest of all three members between this object and other + inline double getAscent() const {return ascent; } + inline double getDescent() const {return descent; } + inline double getLeading() const {return leading; } }; /// see _enum_converter() @@ -717,6 +720,7 @@ float font_size; float x_start; /// relative to the start of the chunk float x_end; /// relative to the start of the chunk + inline float width() const {return std::abs(x_start - x_end);} LineHeight line_height; double baseline_shift; /// relative to the line's baseline Direction direction; /// See CSS3 section 3.2. Either rtl or ltr === modified file 'src/libnrtype/font-instance.h' --- src/libnrtype/font-instance.h 2011-10-04 06:49:12 +0000 +++ src/libnrtype/font-instance.h 2013-04-30 15:52:59 +0000 @@ -59,6 +59,8 @@ double Advance(int glyph_id, bool vertical); // nominal advance of the font. bool FontMetrics(double &ascent, double &descent, double &leading); + bool FontDecoration(double &underline_position, double &underline_thickness, + double &linethrough_position, double &linethrough_thickness); bool FontSlope(double &run, double &rise); // for generating slanted cursors for oblique fonts Geom::OptRect BBox(int glyph_id); === modified file 'src/sp-image.cpp' --- src/sp-image.cpp 2013-04-08 15:06:08 +0000 +++ src/sp-image.cpp 2013-04-30 15:52:59 +0000 @@ -1055,6 +1055,7 @@ t = ti * t; sp_print_image_R8G8B8A8_N(ctx, px + trimx*pixskip + trimy*rs, trimwidth, trimheight, rs, t, item->style); } + free(px); // else big memory leak on each image print! } } === modified file 'src/style.cpp' --- src/style.cpp 2013-04-27 18:23:29 +0000 +++ src/style.cpp 2013-05-17 23:38:53 +0000 @@ -89,7 +89,12 @@ static void sp_style_read_istring(SPIString *val, gchar const *str); static void sp_style_read_ilength(SPILength *val, gchar const *str); static void sp_style_read_ilengthornormal(SPILengthOrNormal *val, gchar const *str); -static void sp_style_read_itextdecoration(SPITextDecoration *val, gchar const *str); + +static void sp_style_read_itextdecoration(SPITextDecorationLine *line, SPITextDecorationStyle *style, SPIPaint *color, const SPIPaint *pcolor, gchar const *str); +static void sp_style_read_itextdecorationLine(SPITextDecorationLine *line, gchar const *str); +static void sp_style_read_itextdecorationStyle(SPITextDecorationStyle *style, gchar const *str); +static void sp_style_read_itextdecorationColor(SPIPaint *color, gchar const *str); + static void sp_style_read_icolor(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document); static void sp_style_read_ifontsize(SPIFontSize *val, gchar const *str); static void sp_style_read_ibaselineshift(SPIBaselineShift *val, gchar const *str); @@ -110,7 +115,14 @@ static gint sp_style_write_ifontsize(gchar *p, gint len, gchar const *key, SPIFontSize const *val, SPIFontSize const *base, guint flags); static gint sp_style_write_ibaselineshift(gchar *p, gint len, gchar const *key, SPIBaselineShift const *val, SPIBaselineShift const *base, guint flags); static gint sp_style_write_ilengthornormal(gchar *p, gint const len, gchar const *const key, SPILengthOrNormal const *const val, SPILengthOrNormal const *const base, guint const flags); -static gint sp_style_write_itextdecoration(gchar *p, gint const len, gchar const *const key, SPITextDecoration const *const val, SPITextDecoration const *const base, guint const flags); +static gint sp_style_write_itextdecoration(gchar *p, gint const len, gchar const *const key, + SPITextDecorationLine const *const line, + SPITextDecorationStyle const *const style, + SPIPaint const *const color, + SPITextDecorationLine const *const baseLine, + SPITextDecorationStyle const *const baseStyle, + SPIPaint const *const baseColor, + guint const flags); static gint sp_style_write_ifilter(gchar *b, gint len, gchar const *key, SPIFilter const *filter, SPIFilter const *base, guint flags); static void sp_style_filter_clear(SPStyle *style); @@ -605,7 +617,7 @@ /* 1. Style attribute */ gchar const *val = repr->attribute("style"); - if (val != NULL) { + if (val != NULL && *val) { sp_style_merge_from_style_string(style, val); } @@ -632,10 +644,10 @@ /* Text (css2 chapter 16) */ SPS_READ_PLENGTH_IF_UNSET(&style->text_indent, repr, "text-indent"); SPS_READ_PENUM_IF_UNSET(&style->text_align, repr, "text-align", enum_text_align, true); - if (!style->text_decoration.set) { - val = repr->attribute("text-decoration"); - if (val) { - sp_style_read_itextdecoration(&style->text_decoration, val); + if (!style->text_decoration_line.set) { + // assume it uses either text-decoration or text-decoration-line, but not both + if ((val = repr->attribute("text-decoration")) || (val = repr->attribute("text-decoration-line"))) { + sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, &style->color, val); } } if (!style->line_height.set) { @@ -1090,8 +1102,23 @@ SPS_READ_IENUM_IF_UNSET(&style->text_align, val, enum_text_align, true); break; case SP_PROP_TEXT_DECORATION: - if (!style->text_decoration.set) { - sp_style_read_itextdecoration(&style->text_decoration, val); + if (!style->text_decoration_line.set) { + sp_style_read_itextdecoration(&style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, &style->color, val); + } + break; + case SP_PROP_TEXT_DECORATION_LINE: + if (!style->text_decoration_line.set) { + sp_style_read_itextdecorationLine(&style->text_decoration_line, val); + } + break; + case SP_PROP_TEXT_DECORATION_STYLE: + if (!style->text_decoration_style.set) { + sp_style_read_itextdecorationStyle(&style->text_decoration_style, val); + } + break; + case SP_PROP_TEXT_DECORATION_COLOR: + if (!style->text_decoration_color.set) { + sp_style_read_itextdecorationColor(&style->text_decoration_color, val); } break; case SP_PROP_LINE_HEIGHT: @@ -1679,11 +1706,23 @@ style->text_align.computed = parent->text_align.computed; } - if (!style->text_decoration.set || style->text_decoration.inherit) { - style->text_decoration.underline = parent->text_decoration.underline; - style->text_decoration.overline = parent->text_decoration.overline; - style->text_decoration.line_through = parent->text_decoration.line_through; - style->text_decoration.blink = parent->text_decoration.blink; + if (!style->text_decoration_line.set || style->text_decoration_line.inherit) { + style->text_decoration_line.underline = parent->text_decoration_line.underline; + style->text_decoration_line.overline = parent->text_decoration_line.overline; + style->text_decoration_line.line_through = parent->text_decoration_line.line_through; + style->text_decoration_line.blink = parent->text_decoration_line.blink; + } + + if (!style->text_decoration_style.set || style->text_decoration_style.inherit) { + style->text_decoration_style.solid = parent->text_decoration_style.solid; + style->text_decoration_style.isdouble = parent->text_decoration_style.isdouble; + style->text_decoration_style.dotted = parent->text_decoration_style.dotted; + style->text_decoration_style.dashed = parent->text_decoration_style.dashed; + style->text_decoration_style.wavy = parent->text_decoration_style.wavy; + } + + if (!style->text_decoration_color.set || style->text_decoration_color.inherit) { + sp_style_merge_ipaint(style, &style->text_decoration_color, &parent->text_decoration_color); } if (!style->line_height.set || style->line_height.inherit) { @@ -2169,8 +2208,9 @@ * property (fixme). This code may need changing once we do the * special fill/stroke inheritance mentioned by the spec. */ - sp_style_merge_prop_from_dying_parent(style->text_decoration, - parent->text_decoration); + sp_style_merge_prop_from_dying_parent( style->text_decoration_line, parent->text_decoration_line); + sp_style_merge_prop_from_dying_parent(style->text_decoration_style, parent->text_decoration_style); + sp_style_merge_paint_prop_from_dying_parent(style,style->text_decoration_color, parent->text_decoration_color); //nyi: font-size-adjust, // | none | inherit //nyi: glyph-orientation-horizontal, @@ -2623,7 +2663,9 @@ /* Text */ p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &style->text_indent, NULL, flags); p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &style->text_align, NULL, flags); - p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", &style->text_decoration, NULL, flags); + p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", + &style->text_decoration_line, &style->text_decoration_style, &style->text_decoration_color, + NULL, NULL, NULL, flags); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &style->line_height, NULL, flags); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &style->letter_spacing, NULL, flags); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "word-spacing", &style->word_spacing, NULL, flags); @@ -2801,7 +2843,10 @@ /* Text */ p += sp_style_write_ilength(p, c + BMAX - p, "text-indent", &from->text_indent, &to->text_indent, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ienum(p, c + BMAX - p, "text-align", enum_text_align, &from->text_align, &to->text_align, SP_STYLE_FLAG_IFDIFF); - p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", &from->text_decoration, &to->text_decoration, SP_STYLE_FLAG_IFDIFF); + p += sp_style_write_itextdecoration(p, c + BMAX - p, "text-decoration", + &from->text_decoration_line, &from->text_decoration_style, &from->text_decoration_color, + &to->text_decoration_line, &to->text_decoration_style, &to->text_decoration_color, + SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "line-height", &from->line_height, &to->line_height, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "letter-spacing", &from->letter_spacing, &to->letter_spacing, SP_STYLE_FLAG_IFDIFF); p += sp_style_write_ilengthornormal(p, c + BMAX - p, "word-spacing", &from->word_spacing, &to->word_spacing, SP_STYLE_FLAG_IFDIFF); @@ -3018,11 +3063,20 @@ style->text_align.set = FALSE; style->text_align.value = style->text_align.computed = SP_CSS_TEXT_ALIGN_START; - style->text_decoration.set = FALSE; - style->text_decoration.underline = FALSE; - style->text_decoration.overline = FALSE; - style->text_decoration.line_through = FALSE; - style->text_decoration.blink = FALSE; + style->text_decoration_line.set = FALSE; + style->text_decoration_line.underline = FALSE; + style->text_decoration_line.overline = FALSE; + style->text_decoration_line.line_through = FALSE; + style->text_decoration_line.blink = FALSE; + + style->text_decoration_style.set = FALSE; + style->text_decoration_style.solid = FALSE; + style->text_decoration_style.isdouble = FALSE; + style->text_decoration_style.dotted = FALSE; + style->text_decoration_style.dashed = FALSE; + style->text_decoration_style.wavy = FALSE; + + style->text_decoration_color.clear(); style->line_height.set = FALSE; style->line_height.unit = SP_CSS_UNIT_PERCENT; @@ -3463,52 +3517,155 @@ * Set SPITextDecoration object from string. */ static void -sp_style_read_itextdecoration(SPITextDecoration *val, gchar const *str) -{ +sp_style_read_itextdecoration(SPITextDecorationLine *line, SPITextDecorationStyle *style, SPIPaint *color, const SPIPaint *pcolor, gchar const *str){ + sp_style_read_itextdecorationLine(line, str); // scans all tokens for line types + sp_style_read_itextdecorationStyle(style, str); // scans all tokens for style types + // the color routine must be fed one token at a time - if multiple colors are found the LAST one is used + const gchar *hstr = str; + while (1) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + gchar *frag = strndup(hstr,slen+1); // only send one piece at a time, since keywords may be intermixed + sp_style_read_itextdecorationColor(color, frag); + free(frag); + if(color->set)break; + if(*str == '\0')break; + hstr = str + 1; + } + str++; + } +} + +/** + * Set SPITextDecorationLine object from string. + * returns true if there was a match, false otherwise + */ +static void +sp_style_read_itextdecorationLine(SPITextDecorationLine *line, gchar const *str){ if (!strcmp(str, "inherit")) { - val->set = TRUE; - val->inherit = TRUE; + line->set = true; + line->inherit = true; } else if (!strcmp(str, "none")) { - val->set = TRUE; - val->inherit = FALSE; - val->underline = FALSE; - val->overline = FALSE; - val->line_through = FALSE; - val->blink = FALSE; + line->set = true; + line->inherit = false; + line->underline = false; + line->overline = false; + line->line_through = false; + line->blink = false; } else { - bool found_underline = false; - bool found_overline = false; + bool found_one = false; + bool hit_one = false; + + // CSS 2 keywords + bool found_underline = false; + bool found_overline = false; bool found_line_through = false; - bool found_blink = false; - for ( ; *str ; str++ ) { - if (*str == ' ') continue; - if (strneq(str, "underline", 9) && (str[9] == ' ' || str[9] == '\0')) { - found_underline = true; - str += 9; - } else if (strneq(str, "overline", 8) && (str[8] == ' ' || str[8] == '\0')) { - found_overline = true; - str += 8; - } else if (strneq(str, "line-through", 12) && (str[12] == ' ' || str[12] == '\0')) { - found_line_through = true; - str += 12; - } else if (strneq(str, "blink", 5) && (str[5] == ' ' || str[5] == '\0')) { - found_blink = true; - str += 5; - } else { - return; // invalid value - } - } - if (!(found_underline || found_overline || found_line_through || found_blink)) { - return; // invalid value: empty - } - val->set = TRUE; - val->inherit = FALSE; - val->underline = found_underline; - val->overline = found_overline; - val->line_through = found_line_through; - val->blink = found_blink; - } -} + bool found_blink = false; + + // this method ignores inlineid keys and extra delimiters, so " ,,, blink hello" will set blink and ignore hello + const gchar *hstr = str; + while (1) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + // CSS 2 keywords + while(1){ // not really a loop, used to avoid a goto + hit_one = true; // most likely we will + if ((slen == 9) && strneq(hstr, "underline", slen)){ found_underline = true; break; } + if ((slen == 8) && strneq(hstr, "overline", slen)){ found_overline = true; break; } + if ((slen == 12) && strneq(hstr, "line-through", slen)){ found_line_through = true; break; } + if ((slen == 5) && strneq(hstr, "blink", slen)){ found_blink = true; break; } + if ((slen == 4) && strneq(hstr, "none", slen)){ break; } + + hit_one = false; // whatever this thing is, we do not recognize it + break; + } + found_one |= hit_one; + if(*str == '\0')break; + hstr = str + 1; + } + str++; + } + if (found_one) { + line->set = true; + line->inherit = false; + line->underline = found_underline; + line->overline = found_overline; + line->line_through = found_line_through; + line->blink = found_blink; + } + else { + line->set = false; + line->inherit = false; + } + } +} + +/** + * Set SPITextDecorationStyle object from string. + * returns true if there was a match, false otherwise +*/ +static void +sp_style_read_itextdecorationStyle(SPITextDecorationStyle *style, gchar const *str){ + if (!strcmp(str, "inherit")) { + style->set = true; + style->inherit = true; + } else if (!strcmp(str, "none")) { + style->set = true; + style->inherit = false; + style->solid = false; + style->isdouble = false; + style->dotted = false; + style->dashed = false; + style->wavy = false; + } else { + // note, these are CSS 3 keywords + bool found_solid = false; + bool found_double = false; + bool found_dotted = false; + bool found_dashed = false; + bool found_wavy = false; + bool found_one = false; + + // this method ignores inlineid keys and extra delimiters, so " ,,, style hello" will set style and ignore hello + // if more than one style is present, the first is used + const gchar *hstr = str; + while (1) { + if (*str == ' ' || *str == ',' || *str == '\0'){ + int slen = str - hstr; + if ( (slen == 5) && strneq(hstr, "solid", slen)){ found_solid = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "double", slen)){ found_double = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "dotted", slen)){ found_dotted = true; found_one = true; break; } + else if ((slen == 6) && strneq(hstr, "dashed", slen)){ found_dashed = true; found_one = true; break; } + else if ((slen == 4) && strneq(hstr, "wavy", slen)){ found_wavy = true; found_one = true; break; } + if(*str == '\0')break; // nothing more to test + hstr = str + 1; + } + str++; + } + if(found_one){ + style->set = true; + style->inherit = false; + style->solid = found_solid; + style->isdouble = found_double; + style->dotted = found_dotted; + style->dashed = found_dashed; + style->wavy = found_wavy; + } + else { + style->set = false; + style->inherit = false; + } + } +} + +/** + * Set SPIPaint object from string. + */ +static void +sp_style_read_itextdecorationColor(SPIPaint *color, gchar const *str){ + sp_style_read_icolor(color, str, NULL,NULL); +} + /** * Set SPIPaint object from string containing an integer value. @@ -3524,11 +3681,11 @@ paint->set = TRUE; paint->inherit = TRUE; } else { + paint->inherit = FALSE; guint32 const rgb0 = sp_svg_read_color(str, 0xff); if (rgb0 != 0xff) { paint->setColor(rgb0); paint->set = TRUE; - paint->inherit = FALSE; } } } @@ -4066,12 +4223,39 @@ * */ static bool -sp_textdecoration_differ(SPITextDecoration const *const a, SPITextDecoration const *const b) -{ - return a->underline != b->underline - || a->overline != b->overline - || a->line_through != b->line_through - || a->blink != b->blink; +sp_textdecorationLine_differ(SPITextDecorationLine const *const a, SPITextDecorationLine const *const b) +{ + return( (a->underline != b->underline ) + || (a->overline != b->overline ) + || (a->line_through != b->line_through) + || (a->blink != b->blink ) + ); +} + +/** + * + */ +static bool +sp_textdecorationStyle_differ(SPITextDecorationStyle const *const a, SPITextDecorationStyle const *const b) +{ + return( (a->solid != b->solid ) + || (a->isdouble != b->isdouble ) + || (a->dotted != b->dotted ) + || (a->dashed != b->dashed ) + || (a->wavy != b->wavy ) + ); +} + +/** + * + */ +static bool +sp_textdecorationColor_differ(SPIPaint const *const a, SPIPaint const *const b) +{ + bool status = (a->isPaintserver() == b->isPaintserver()) && + (a->colorSet == b->colorSet) && + (a->currentcolor == b->currentcolor); + return(status); } /** @@ -4079,31 +4263,55 @@ */ static gint sp_style_write_itextdecoration(gchar *p, gint const len, gchar const *const key, - SPITextDecoration const *const val, - SPITextDecoration const *const base, + SPITextDecorationLine const *const line, + SPITextDecorationStyle const *const style, + SPIPaint const *const color, + SPITextDecorationLine const *const baseLine, + SPITextDecorationStyle const *const baseStyle, + SPIPaint const *const baseColor, guint const flags) { Inkscape::CSSOStringStream os; - if ((flags & SP_STYLE_FLAG_ALWAYS) - || ((flags & SP_STYLE_FLAG_IFSET) && val->set) - || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set - && (!base->set || sp_textdecoration_differ(val, base)))) - { - if (val->inherit) { - return g_snprintf(p, len, "%s:inherit;", key); - } else { - os << key << ":"; - if (val->underline || val->overline || val->line_through || val->blink) { - if (val->underline) os << " underline"; - if (val->overline) os << " overline"; - if (val->line_through) os << " line-through"; - if (val->blink) os << " blink"; - } else - os << "none"; - os << ";"; - return g_strlcpy(p, os.str().c_str(), len); - } + if ( (flags & SP_STYLE_FLAG_ALWAYS) + || ((flags & SP_STYLE_FLAG_IFSET) && line->set) + || ((flags & SP_STYLE_FLAG_IFDIFF) && line->set + && ( !baseLine->set || sp_textdecorationLine_differ(line, baseLine))) + || ((flags & SP_STYLE_FLAG_IFSET) && style->set) + || ((flags & SP_STYLE_FLAG_IFDIFF) && style->set + && ( !baseStyle->set || sp_textdecorationStyle_differ(style, baseStyle))) + || ((flags & SP_STYLE_FLAG_IFSET) && color->set) + || ((flags & SP_STYLE_FLAG_IFDIFF) && color->set + && ( !baseColor->set || sp_textdecorationColor_differ(color, baseColor))) + ){ + os << key << ":"; + if (line->inherit || style->inherit || color->inherit) { + os << " inherit"; + } + else if (line->underline || line->overline || line->line_through || line->blink) { + if (line->underline) os << " underline"; + if (line->overline) os << " overline"; + if (line->line_through) os << " line-through"; + if (line->blink) os << " blink"; + + if ( style->solid) os << " solid"; + else if (style->isdouble) os << " double"; + else if (style->dotted) os << " dotted"; + else if (style->dashed) os << " dashed"; + else if (style->wavy) os << " wavy"; + // color, if it is set, otherwise omit it + if(color->set){ + char color_buf[8]; + sp_svg_write_color(color_buf, sizeof(color_buf), color->value.color.toRGBA32( 0 )); + os << " "; + os << color_buf; + } + } + else { + os << "none"; + } + os << ";"; + return g_strlcpy(p, os.str().c_str(), len); } return 0; } === modified file 'src/style.h' --- src/style.h 2013-04-27 18:23:29 +0000 +++ src/style.h 2013-05-20 19:57:47 +0000 @@ -229,8 +229,9 @@ float computed; }; +// CSS 2. Changes in CSS 3, where description is for TextDecorationLine, NOT TextDecoration /// Text decoration type internal to SPStyle. -struct SPITextDecoration { +struct SPITextDecorationLine { unsigned set : 1; unsigned inherit : 1; unsigned underline : 1; @@ -239,6 +240,18 @@ unsigned blink : 1; // "Conforming user agents are not required to support this value." yay! }; +// CSS3 2.2 +/// Text decoration style type internal to SPStyle. +struct SPITextDecorationStyle { + unsigned set : 1; + unsigned inherit : 1; + unsigned solid : 1; + unsigned isdouble : 1; // cannot use "double" as it is a reserved keyword + unsigned dotted : 1; + unsigned dashed : 1; + unsigned wavy : 1; +}; + /// Extended length type internal to SPStyle. struct SPILengthOrNormal { unsigned set : 1; @@ -249,6 +262,21 @@ float computed; }; +// These are used to implement text_decoration. The values are not saved to or read from SVG file +struct SPITextDecorationData { + float phase_length; // length along text line,used for phase for dot/dash/wavy + bool tspan_line_start; // is first span on a line + bool tspan_line_end; // is last span on a line + float tspan_width; // from libnrtype, when it calculates spans + float ascender; // the rest from tspan's font + float descender; + float line_gap; + float underline_thickness; + float underline_position; + float line_through_thickness; + float line_through_position; +}; + struct SPTextStyle; /// Stroke dash details. @@ -289,8 +317,18 @@ SPILength text_indent; /** text alignment (css2 16.2) (not to be confused with text-anchor) */ SPIEnum text_align; - /** text decoration (css2 16.3.1) */ - SPITextDecoration text_decoration; + /** text decoration (css2 16.3.1) is now handled as a subset of css3 2.4 */ + // SPITextDecoration text_decoration; + + /** CSS 3 2.1, 2.2, 2.3 */ + /** Not done yet, test_decoration3 = css3 2.4*/ + SPITextDecorationLine text_decoration_line; + SPIPaint text_decoration_color; + SPITextDecorationStyle text_decoration_style; + + // used to implement text_decoration, not saved to or read from SVG file + SPITextDecorationData text_decoration_data; + // 16.3.2 is text-shadow. That's complicated. /** Line spacing (css2 10.8.1) */ SPILengthOrNormal line_height;