Code

Add support for baseline-shift attribute (all possible values).
authortavmjong-free <tavmjong@free.fr>
Fri, 4 Jun 2010 19:41:52 +0000 (21:41 +0200)
committertavmjong-free <tavmjong@free.fr>
Fri, 4 Jun 2010 19:41:52 +0000 (21:41 +0200)
Add superscript and subscript support to Text toolbar.
Missing icons for superscript and subscript.
Known bug: adding a character to a superscript or subscript text
string resets baseline-shift attribute.

src/desktop-style.cpp
src/desktop-style.h
src/libnrtype/FontInstance.cpp
src/libnrtype/Layout-TNG-Compute.cpp
src/sp-text.cpp
src/style.cpp
src/style.h
src/widgets/toolbox.cpp

index 2225be5ee6447cfc94c44939a97b67e711223c1a..2c2a99aee6b76e5fccb573259a47c96fa8030719 100644 (file)
@@ -1051,6 +1051,101 @@ objects_query_fontstyle (GSList *objects, SPStyle *style_res)
     }
 }
 
+/**
+ * Write to style_res the baseline numbers.
+ */
+int
+objects_query_baselines (GSList *objects, SPStyle *style_res)
+{
+    bool different = false;
+
+    // Only baseline-shift at the moment
+    // We will return:
+    //   If baseline-shift is same for all objects:
+    //     The full baseline-shift data (used for subscripts and superscripts)
+    //   If baseline-shift is different:
+    //     The average baseline-shift (not implemented at the moment as this is complicated June 2010)
+    SPIBaselineShift old;
+    old.value = 0.0;
+    old.computed = 0.0;
+
+    // double baselineshift = 0.0;
+    bool set = false;
+
+    int texts = 0;
+
+    for (GSList const *i = objects; i != NULL; i = i->next) {
+        SPObject *obj = SP_OBJECT (i->data);
+
+        if (!SP_IS_TEXT(obj) && !SP_IS_FLOWTEXT(obj)
+            && !SP_IS_TSPAN(obj) && !SP_IS_TREF(obj) && !SP_IS_TEXTPATH(obj)
+            && !SP_IS_FLOWDIV(obj) && !SP_IS_FLOWPARA(obj) && !SP_IS_FLOWTSPAN(obj))
+            continue;
+
+        SPStyle *style = SP_OBJECT_STYLE (obj);
+        if (!style) continue;
+
+        texts ++;
+
+        SPIBaselineShift current;
+        if(style->baseline_shift.set) {
+
+            current.set      = style->baseline_shift.set;
+            current.inherit  = style->baseline_shift.inherit;
+            current.type     = style->baseline_shift.type;
+            current.literal  = style->baseline_shift.literal;
+            current.value    = style->baseline_shift.value;
+            current.computed = style->baseline_shift.computed;
+
+            if( set ) {
+                if( current.set      != old.set ||
+                    current.inherit  != old.inherit ||
+                    current.type     != old.type ||
+                    current.literal  != old.literal ||
+                    current.value    != old.value ||
+                    current.computed != old.computed ) {
+                    // Maybe this needs to be better thought out.
+                    different = true;
+                }
+            }
+
+            set = true;
+
+            old.set      = current.set;
+            old.inherit  = current.inherit;
+            old.type     = current.type;
+            old.literal  = current.literal;
+            old.value    = current.value;
+            old.computed = current.computed;
+        }
+    }
+
+    if (different || !set ) {
+        style_res->baseline_shift.set = false;
+        style_res->baseline_shift.computed = 0.0;
+    } else {
+        style_res->baseline_shift.set      = old.set;
+        style_res->baseline_shift.inherit  = old.inherit;
+        style_res->baseline_shift.type     = old.type;
+        style_res->baseline_shift.literal  = old.literal;
+        style_res->baseline_shift.value    = old.value;
+        style_res->baseline_shift.computed = old.computed;
+    }
+
+    if (texts == 0 || !set)
+        return QUERY_STYLE_NOTHING;
+
+    if (texts > 1) {
+        if (different) {
+            return QUERY_STYLE_MULTIPLE_DIFFERENT;
+        } else {
+            return QUERY_STYLE_MULTIPLE_SAME;
+        }
+    } else {
+        return QUERY_STYLE_SINGLE;
+    }
+}
+
 /**
  * Write to style_res the average font family of objects.
  */
@@ -1346,6 +1441,8 @@ sp_desktop_query_style_from_list (GSList *list, SPStyle *style, int property)
         return objects_query_fontstyle (list, style);
     } else if (property == QUERY_STYLE_PROPERTY_FONTNUMBERS) {
         return objects_query_fontnumbers (list, style);
+    } else if (property == QUERY_STYLE_PROPERTY_BASELINES) {
+        return objects_query_baselines (list, style);
 
     } else if (property == QUERY_STYLE_PROPERTY_BLEND) {
         return objects_query_blend (list, style);
index 40461da723cea5b8909f0855db1538fad0f0a4bb..e1ca5b3e7db4009aae1b3d80c98a95d24764d154 100644 (file)
@@ -48,6 +48,7 @@ enum { // which property was queried (add when you need more)
     QUERY_STYLE_PROPERTY_FONTFAMILY, // font-family
     QUERY_STYLE_PROPERTY_FONTSTYLE, // font style 
     QUERY_STYLE_PROPERTY_FONTNUMBERS, // size, spacings
+    QUERY_STYLE_PROPERTY_BASELINES, // baseline-shift
     QUERY_STYLE_PROPERTY_MASTEROPACITY, // opacity
     QUERY_STYLE_PROPERTY_BLEND, // blend
     QUERY_STYLE_PROPERTY_BLUR // blur
index b958e899dca6c713db228cbc687c6f590ab07dd9..bdf7003464d58470919a2c9309f53259c7a212f4 100644 (file)
@@ -412,7 +412,7 @@ void font_instance::InitTheFace()
     SetGraphicsMode(daddy->hScreenDC, GM_COMPATIBLE);
     SelectObject(daddy->hScreenDC,theFace);
 #else
-    theFace=pango_ft2_font_get_face(pFont);
+    theFace=pango_ft2_font_get_face(pFont); // Deprecated, use pango_fc_font_lock_face() instead
     if ( theFace ) {
         FT_Select_Charmap(theFace,ft_encoding_unicode) && FT_Select_Charmap(theFace,ft_encoding_symbol);
     }
@@ -662,6 +662,7 @@ bool font_instance::FontMetrics(double &ascent,double &descent,double &leading)
     ascent=fabs(otm.otmAscent*scale);
     descent=fabs(otm.otmDescent*scale);
     leading=fabs(otm.otmLineGap*scale);
+    //otmSubscriptSize, otmSubscriptOffset, otmSuperscriptSize, otmSuperscriptOffset, 
 #else
     if ( theFace->units_per_EM == 0 ) {
         return false; // bitmap font
index cad2f9b19996a598e3dda61f6498c4f34d6b0934..192596ecf2b96d2a04767b1133880066c0f471e9 100644 (file)
@@ -124,8 +124,9 @@ class Layout::Calculator
         unsigned input_index;         /// index into Layout::_input_stream
         Glib::ustring::const_iterator input_stream_first_character;
         double font_size;
-        LineHeight line_height;
+        LineHeight line_height;         /// This is not the CSS line-height attribute!
         double line_height_multiplier;  /// calculated from the font-height css property
+        double baseline_shift;          /// calculated from the baseline-shift css property
         unsigned text_bytes;
         unsigned char_index_in_para;    /// the index of the first character in this span in the paragraph, for looking up char_attributes
         SVGLength x, y, dx, dy, rotate;  // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
@@ -521,7 +522,7 @@ class Layout::Calculator
                 new_span.in_chunk = _flow._chunks.size() - 1;
                 new_span.line_height = unbroken_span.line_height;
                 new_span.in_input_stream_item = unbroken_span.input_index;
-                new_span.baseline_shift = _y_offset;
+                new_span.baseline_shift = 0.0;
                 new_span.block_progression = _block_progression;
                 if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
                     {
@@ -604,11 +605,16 @@ class Layout::Calculator
 
                         if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
-                            new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
+                            new_glyph.y = _y_offset -
+                                unbroken_span.baseline_shift +
+                                (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset -
+                                 unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
                             new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true);
                         } else {
                             new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
-                            new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
+                            new_glyph.y = _y_offset -
+                                unbroken_span.baseline_shift +
+                                unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
                             new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
                             if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font))
                                 new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false);
@@ -984,6 +990,7 @@ void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font
     *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
 }
 
+
 /**
  * Split the paragraph into spans. Also call pango_shape() on them.
  *
@@ -1130,6 +1137,11 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
                     }
                     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);
+
+                    // At some point we may want to calculate baseline_shift here (to take advantage
+                    // of otm features like superscript baseline), but for now we use style baseline_shift.
+                    new_span.baseline_shift = text_source->style->baseline_shift.computed;
+
                     // TODO: metrics for vertical text
                     TRACE(("add text span %d \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
                     TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
index 0f21b59d19a237b0129942442e89db47661d1cbf..bae625f5837317903e8201a38a3418361d04ca1f 100644 (file)
@@ -528,6 +528,8 @@ unsigned SPText::_buildLayoutInput(SPObject *root, Inkscape::Text::Layout::Optio
     }
     else if (SP_IS_TSPAN(root)) {
         SPTSpan *tspan = SP_TSPAN(root);
+        // x, y attributes are stripped from some tspans as we do our own line layout
+        // This should be checked carefully, as it can undo line layout in imported SVG files.
         bool use_xy = !in_textpath && (tspan->role == SP_TSPAN_ROLE_UNSPECIFIED || !tspan->attributes.singleXYCoordinates());
         tspan->attributes.mergeInto(&optional_attrs, parent_optional_attrs, parent_attrs_offset, use_xy, true);
     }
index a05cef252c1d789d0e0a7b88d813c66c159df1f4..f2686287184228017fadde3950814b4bd8024321 100644 (file)
@@ -88,11 +88,13 @@ static void sp_style_read_itextdecoration(SPITextDecoration *val, gchar const *s
 static void sp_style_read_icolor(SPIPaint *paint, gchar const *str, SPStyle *style, SPDocument *document);
 static void sp_style_read_ipaint(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);
 static void sp_style_read_ifilter(gchar const *str, SPStyle *style, SPDocument *document);
 
 static void sp_style_read_penum(SPIEnum *val, Inkscape::XML::Node *repr, gchar const *key, SPStyleEnum const *dict, bool can_explicitly_inherit);
 static void sp_style_read_plength(SPILength *val, Inkscape::XML::Node *repr, gchar const *key);
 static void sp_style_read_pfontsize(SPIFontSize *val, Inkscape::XML::Node *repr, gchar const *key);
+static void sp_style_read_pbaselineshift(SPIBaselineShift *val, Inkscape::XML::Node *repr, gchar const *key);
 static void sp_style_read_pfloat(SPIFloat *val, Inkscape::XML::Node *repr, gchar const *key);
 
 static gint sp_style_write_ifloat(gchar *p, gint len, gchar const *key, SPIFloat const *val, SPIFloat const *base, guint flags);
@@ -102,6 +104,7 @@ static gint sp_style_write_istring(gchar *p, gint len, gchar const *key, SPIStri
 static gint sp_style_write_ilength(gchar *p, gint len, gchar const *key, SPILength const *val, SPILength const *base, guint flags);
 static gint sp_style_write_ipaint(gchar *b, gint len, gchar const *key, SPIPaint const *paint, SPIPaint const *base, guint flags);
 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_ifilter(gchar *b, gint len, gchar const *key, SPIFilter const *filter, SPIFilter const *base, guint flags);
@@ -119,6 +122,9 @@ static void sp_style_filter_clear(SPStyle *style);
 #define SPS_READ_IFONTSIZE_IF_UNSET(v,s) if (!(v)->set) {sp_style_read_ifontsize((v), (s));}
 #define SPS_READ_PFONTSIZE_IF_UNSET(v,r,k) if (!(v)->set) {sp_style_read_pfontsize((v), (r), (k));}
 
+#define SPS_READ_IBASELINE_SHIFT_IF_UNSET(v,s) if (!(v)->set) {sp_style_read_ibaselineshift((v), (s));}
+#define SPS_READ_PBASELINE_SHIFT_IF_UNSET(v,r,k) if (!(v)->set) {sp_style_read_pbaselineshift((v), (r), (k));}
+
 static void sp_style_merge_from_object_stylesheet(SPStyle *, SPObject const *);
 
 struct SPStyleEnum {
@@ -259,6 +265,13 @@ static SPStyleEnum const enum_writing_mode[] = {
     {NULL, -1}
 };
 
+static SPStyleEnum const enum_baseline_shift[] = {
+    {"baseline", SP_CSS_BASELINE_SHIFT_BASELINE},
+    {"sub",      SP_CSS_BASELINE_SHIFT_SUB},
+    {"super",    SP_CSS_BASELINE_SHIFT_SUPER},
+    {NULL, -1}
+};
+
 static SPStyleEnum const enum_visibility[] = {
     {"hidden", SP_CSS_VISIBILITY_HIDDEN},
     {"collapse", SP_CSS_VISIBILITY_COLLAPSE},
@@ -628,6 +641,7 @@ sp_style_read(SPStyle *style, SPObject *object, Inkscape::XML::Node *repr)
                             enum_writing_mode, true);
     SPS_READ_PENUM_IF_UNSET(&style->text_anchor, repr, "text-anchor",
                             enum_text_anchor, true);
+    SPS_READ_PBASELINE_SHIFT_IF_UNSET(&style->baseline_shift, repr, "baseline-shift");
 
     /* opacity */
     if (!style->opacity.set) {
@@ -945,6 +959,9 @@ sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
         case SP_PROP_TEXT_ANCHOR:
             SPS_READ_IENUM_IF_UNSET(&style->text_anchor, val, enum_text_anchor, true);
             break;
+        case SP_PROP_BASELINE_SHIFT:
+            SPS_READ_IBASELINE_SHIFT_IF_UNSET(&style->baseline_shift, val);
+            break;
             /* Text (unimplemented) */
         case SP_PROP_TEXT_RENDERING: {
             /* Ignore the hint. */
@@ -955,9 +972,6 @@ sp_style_merge_property(SPStyle *style, gint id, gchar const *val)
         case SP_PROP_ALIGNMENT_BASELINE:
             g_warning("Unimplemented style property SP_PROP_ALIGNMENT_BASELINE: value: %s", val);
             break;
-        case SP_PROP_BASELINE_SHIFT:
-            g_warning("Unimplemented style property SP_PROP_BASELINE_SHIFT: value: %s", val);
-            break;
         case SP_PROP_DOMINANT_BASELINE:
             g_warning("Unimplemented style property SP_PROP_DOMINANT_BASELINE: value: %s", val);
             break;
@@ -1342,6 +1356,48 @@ sp_style_merge_font_size_from_parent(SPIFontSize &child, SPIFontSize const &pare
     }
 }
 
+// Some shifts are defined relative to parent.
+static void
+sp_style_merge_baseline_shift_from_parent(SPIBaselineShift &child, SPIBaselineShift const &parent,
+                                          SPIFontSize const &pfont_size)
+{
+    /* 'baseline-shift' */
+    if (!child.set || child.inherit) {
+        /* Inherit the computed value.  Reference: http://www.w3.org/TR/SVG11/styling.html#Inheritance */
+        child.computed = parent.computed;  // Does this make sense (applying a shift a second time)?
+    } else if (child.type == SP_BASELINE_SHIFT_LITERAL) {
+        if( child.literal == SP_CSS_BASELINE_SHIFT_BASELINE ) {
+            child.computed = 0; // No change
+        } else if (child.literal == SP_CSS_BASELINE_SHIFT_SUB ) {
+            // Should use subscript position from font relative to alphabetic baseline
+            // In mean time use values from OpenOffice and Adobe
+            child.computed = -0.33 * pfont_size.computed; 
+        } else if (child.literal == SP_CSS_BASELINE_SHIFT_SUPER ) {
+            // Should use superscript position from font relative to alphabetic baseline
+            // In mean time use values from OpenOffice and Adobe
+            child.computed =  0.33 * pfont_size.computed; 
+        } else {
+            /* Illegal value */
+        }
+    } else if (child.type == SP_BASELINE_SHIFT_PERCENTAGE) {
+        // Percentage for baseline shift is relative to computed "line-height"
+        // which is just font-size (see SVG1.1 'font').
+        child.computed = pfont_size.computed * child.value;
+    } else if (child.type == SP_BASELINE_SHIFT_LENGTH) {
+        switch (child.unit) {
+            case SP_CSS_UNIT_EM:
+                child.computed = child.value * pfont_size.computed; 
+                break;
+            case SP_CSS_UNIT_EX:
+                child.computed = child.value * 0.5 * pfont_size.computed; 
+                break;
+            default:
+                /* No change */
+                break;
+        }
+    }
+}
+
 /**
  * Sets computed values in \a style, which may involve inheriting from (or in some other way
  * calculating from) corresponding computed values of \a parent.
@@ -1483,6 +1539,10 @@ sp_style_merge_from_parent(SPStyle *const style, SPStyle const *const parent)
         style->text_anchor.computed = parent->text_anchor.computed;
     }
 
+    /* Baseline Shift... Some shifts are relative to parent. */
+    sp_style_merge_baseline_shift_from_parent(style->baseline_shift, parent->baseline_shift,
+                                              parent->font_size);
+
     if (style->opacity.inherit) {
         style->opacity.value = parent->opacity.value;
     }
@@ -2269,6 +2329,8 @@ sp_style_write_string(SPStyle const *const style, guint const flags)
     p += sp_style_write_ienum(p, c + BMAX - p, "writing-mode", enum_writing_mode, &style->writing_mode, NULL, flags);
 
     p += sp_style_write_ienum(p, c + BMAX - p, "text-anchor", enum_text_anchor, &style->text_anchor, NULL, flags);
+    p += sp_style_write_ibaselineshift(p, c + BMAX - p, "baseline-shift", &style->baseline_shift, NULL, flags);
+
 
     /// \todo fixme: Per type methods need default flag too (lauris)
 
@@ -2433,6 +2495,7 @@ sp_style_write_difference(SPStyle const *const from, SPStyle const *const to)
     p += sp_style_write_ienum(p, c + BMAX - p, "writing-mode", enum_writing_mode, &from->writing_mode, &to->writing_mode, SP_STYLE_FLAG_IFDIFF);
 
     p += sp_style_write_ienum(p, c + BMAX - p, "text-anchor", enum_text_anchor, &from->text_anchor, &to->text_anchor, SP_STYLE_FLAG_IFDIFF);
+    p += sp_style_write_ibaselineshift(p, c + BMAX - p, "baseline-shift", &from->baseline_shift, &to->baseline_shift, SP_STYLE_FLAG_IFDIFF);
 
     /// \todo fixme: Per type methods need default flag too
     if (from->opacity.set && from->opacity.value != SP_SCALE24_MAX) {
@@ -2650,6 +2713,13 @@ sp_style_clear(SPStyle *style)
     style->word_spacing.normal = TRUE;
     style->word_spacing.value = style->word_spacing.computed = 0.0;
 
+    style->baseline_shift.set = FALSE;
+    style->baseline_shift.type = SP_BASELINE_SHIFT_LITERAL;
+    style->baseline_shift.unit = SP_CSS_UNIT_NONE;
+    style->baseline_shift.literal = SP_CSS_BASELINE_SHIFT_BASELINE; 
+    style->baseline_shift.value = 0.0;
+    style->baseline_shift.computed = 0.0;
+
     style->text_transform.set = FALSE;
     style->text_transform.value = style->text_transform.computed = SP_CSS_TEXT_TRANSFORM_NONE;
 
@@ -2662,7 +2732,6 @@ sp_style_clear(SPStyle *style)
     style->writing_mode.set = FALSE;
     style->writing_mode.value = style->writing_mode.computed = SP_CSS_WRITING_MODE_LR_TB;
 
-
     style->text_anchor.set = FALSE;
     style->text_anchor.value = style->text_anchor.computed = SP_CSS_TEXT_ANCHOR_START;
 
@@ -3216,6 +3285,7 @@ sp_style_read_ifontsize(SPIFontSize *val, gchar const *str)
         val->set = TRUE;
         val->inherit = TRUE;
     } else if ((*str == 'x') || (*str == 's') || (*str == 'm') || (*str == 'l')) {
+        // xx-small, x-small, etc.
         for (unsigned i = 0; enum_font_size[i].key; i++) {
             if (!strcmp(str, enum_font_size[i].key)) {
                 val->set = TRUE;
@@ -3271,6 +3341,45 @@ sp_style_read_ifontsize(SPIFontSize *val, gchar const *str)
 }
 
 
+/**
+ * Set SPIBaselineShift object from string.
+ */
+static void
+sp_style_read_ibaselineshift(SPIBaselineShift *val, gchar const *str)
+{
+    if (!strcmp(str, "inherit")) {
+        val->set = TRUE;
+        val->inherit = TRUE;
+    } else if ((*str == 'b') || (*str == 's')) {
+        // baseline or sub or super
+        for (unsigned i = 0; enum_baseline_shift[i].key; i++) {
+            if (!strcmp(str, enum_baseline_shift[i].key)) {
+                val->set = TRUE;
+                val->inherit = FALSE;
+                val->type = SP_BASELINE_SHIFT_LITERAL;
+                val->literal = enum_baseline_shift[i].value;
+                return;
+            }
+        }
+        /* Invalid */
+        return;
+    } else {
+        SPILength length;
+        sp_style_read_ilength(&length, str);
+        val->set      = length.set;
+        val->inherit  = length.inherit;
+        val->unit     = length.unit;
+        val->value    = length.value;
+        val->computed = length.computed;
+        if( val->unit == SP_CSS_UNIT_PERCENT ) {
+            val->type = SP_BASELINE_SHIFT_PERCENTAGE;
+        } else {
+            val->type = SP_BASELINE_SHIFT_LENGTH;
+        }
+        return;
+    }
+}
+
 
 /**
  * Set SPIFilter object from string.
@@ -3368,6 +3477,19 @@ sp_style_read_pfontsize(SPIFontSize *val, Inkscape::XML::Node *repr, gchar const
 }
 
 
+/**
+ * Set SPIBaselineShift object from repr attribute.
+ */
+static void
+sp_style_read_pbaselineshift(SPIBaselineShift *val, Inkscape::XML::Node *repr, gchar const *key)
+{
+    gchar const *str = repr->attribute(key);
+    if (str) {
+        sp_style_read_ibaselineshift(val, str);
+    }
+}
+
+
 /**
  * Set SPIFloat object from repr attribute.
  */
@@ -3828,6 +3950,77 @@ sp_style_write_ifontsize(gchar *p, gint const len, gchar const *key,
 }
 
 
+/**
+ *
+ */
+static bool
+sp_baseline_shift_differ(SPIBaselineShift const *const a, SPIBaselineShift const *const b)
+{
+    if (a->type != b->type)
+        return true;
+    if (a->type == SP_BASELINE_SHIFT_LITERAL ) {
+        if (a->literal != b->literal)
+            return true;
+    }
+    if (a->type == SP_BASELINE_SHIFT_LENGTH) {
+        if (a->unit == SP_CSS_UNIT_EM || a->unit == SP_CSS_UNIT_EX ) {
+            if( a->value != b->value )
+                return true;
+        } else {
+            if (a->computed != b->computed)
+            return true;
+        }
+    }
+    if (a->type == SP_BASELINE_SHIFT_PERCENTAGE) {
+        if (a->value != b->value)
+            return true;
+    }
+    return false;
+}
+
+
+/**
+ * Write SPIBaselineShift object into string.
+ */
+static gint
+sp_style_write_ibaselineshift(gchar *p, gint const len, gchar const *key,
+                              SPIBaselineShift const *const val, SPIBaselineShift const *const base,
+                              guint const flags)
+{
+    if ((flags & SP_STYLE_FLAG_ALWAYS)
+        || ((flags & SP_STYLE_FLAG_IFSET) && val->set)
+        || ((flags & SP_STYLE_FLAG_IFDIFF) && val->set
+            && (!base->set || sp_baseline_shift_differ(val, base))))
+    {
+        if (val->inherit) {
+            return g_snprintf(p, len, "%s:inherit;", key);
+        } else if (val->type == SP_BASELINE_SHIFT_LITERAL) {
+            for (unsigned i = 0; enum_baseline_shift[i].key; i++) {
+                if (enum_baseline_shift[i].value == static_cast< gint > (val->value) ) {
+                    return g_snprintf(p, len, "%s:%s;", key, enum_baseline_shift[i].key);
+                }
+            }
+        } else if (val->type == SP_BASELINE_SHIFT_LENGTH) {
+            if( val->unit == SP_CSS_UNIT_EM || val->unit == SP_CSS_UNIT_EX ) {
+                Inkscape::CSSOStringStream os;
+                os << key << ":" << val->value << (val->unit == SP_CSS_UNIT_EM ? "em;" : "ex;");
+                return g_strlcpy(p, os.str().c_str(), len);
+            } else {
+                Inkscape::CSSOStringStream os;
+                os << key << ":" << val->computed << "px;";      // must specify px, see inkscape bug 1221626, mozilla bug 234789
+                return g_strlcpy(p, os.str().c_str(), len);
+            }
+        } else if (val->type == SP_BASELINE_SHIFT_PERCENTAGE) {
+            Inkscape::CSSOStringStream os;
+            os << key << ":" << (val->value * 100.0) << "%;";
+            return g_strlcpy(p, os.str().c_str(), len);
+        }
+    }
+    return 0;
+}
+
+
+
 /**
  * Write SPIFilter object into string.
  */
@@ -4052,7 +4245,7 @@ sp_css_attr_unset_text(SPCSSAttr *css)
     sp_repr_css_set_property(css, "kerning", NULL); // not implemented yet
     sp_repr_css_set_property(css, "dominant-baseline", NULL); // not implemented yet
     sp_repr_css_set_property(css, "alignment-baseline", NULL); // not implemented yet
-    sp_repr_css_set_property(css, "baseline-shift", NULL); // not implemented yet
+    sp_repr_css_set_property(css, "baseline-shift", NULL);
 
     return css;
 }
index f1b5ec534cb622263372bb439e22cc174e8c15b4..02ef41bf805b9c01a23e65b64f4bc1fcc4d9a6fe 100644 (file)
@@ -43,6 +43,7 @@ class SPIString;
 class SPILength;
 class SPIPaint;
 class SPIFontSize;
+class SPIBaselineShift;
 
 /// Float type internal to SPStyle.
 struct SPIFloat {
@@ -190,6 +191,12 @@ enum {
     SP_FONT_SIZE_PERCENTAGE
 };
 
+enum {
+    SP_BASELINE_SHIFT_LITERAL,
+    SP_BASELINE_SHIFT_LENGTH,
+    SP_BASELINE_SHIFT_PERCENTAGE
+};
+
 #define SP_FONT_SIZE ((1 << 24) - 1)
 
 #define SP_F8_16_TO_FLOAT(v) ((gdouble) (v) / (1 << 16))
@@ -208,6 +215,17 @@ struct SPIFontSize {
     float computed;
 };
 
+/// Baseline shift type internal to SPStyle.
+struct SPIBaselineShift {
+    unsigned set : 1;
+    unsigned inherit : 1;
+    unsigned type : 2;
+    unsigned unit : 4;
+    unsigned literal: 2;
+    float value; // Can be negative
+    float computed;
+};
+
 /// Text decoration type internal to SPStyle.
 struct SPITextDecoration {
     unsigned set : 1;
@@ -287,6 +305,8 @@ struct SPStyle {
     SPIEnum block_progression;
     /** Writing mode (css3 text 3.2 and svg1.1 10.7.2) */
     SPIEnum writing_mode;
+    /** Baseline shift (svg1.1 10.9.2) */
+    SPIBaselineShift baseline_shift;
 
     /* SVG */
     /** Anchor of the text (svg1.1 10.9.1) */
@@ -500,6 +520,12 @@ enum SPTextAnchor {
     SP_CSS_TEXT_ANCHOR_END
 };
 
+enum SPCSSBaselineShift {
+  SP_CSS_BASELINE_SHIFT_BASELINE,
+  SP_CSS_BASELINE_SHIFT_SUB,
+  SP_CSS_BASELINE_SHIFT_SUPER
+};
+
 enum SPVisibility {
     SP_CSS_VISIBILITY_HIDDEN,
     SP_CSS_VISIBILITY_COLLAPSE,
index f3cfe621d5711df6b74bfaea52a5d7b54a46dcbb..502de27df66072b22efc7ae11a606d1f5b30621f 100644 (file)
@@ -489,6 +489,9 @@ static gchar const * ui_descr =
         "    <separator />"
         "    <toolitem action='TextAlignAction' />"
         "    <separator />"
+        "    <toolitem action='TextSuperscriptAction' />"
+        "    <toolitem action='TextSubscriptAction' />"
+        "    <separator />"
         "    <toolitem action='TextLineHeightAction' />"
         "    <toolitem action='TextLetterSpacingAction' />"
         "    <toolitem action='TextWordSpacingAction' />"
@@ -6610,6 +6613,79 @@ static void sp_text_style_changed( InkToggleAction* act, GObject *tbl )
     g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
 }
 
+// Handles both Superscripts and Subscripts
+static void sp_text_script_changed( InkToggleAction* act, GObject *tbl )
+{
+    // quit if run by the _changed callbacks
+    if (g_object_get_data(G_OBJECT(tbl), "freeze")) {
+        return;
+    }
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE) );
+
+    // Called by Superscript or Subscript button?
+    const gchar* name = gtk_action_get_name( GTK_ACTION( act ) );
+    gint prop = (strcmp(name, "TextSuperscriptAction") == 0) ? 0 : 1;
+
+#ifdef DEBUG_TEXT
+    std::cout << "sp_text_script_changed: " << prop << std::endl;
+#endif
+
+    // Query baseline
+    SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
+    int result_baseline = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_BASELINES);
+
+    bool setSuper = false;
+    bool setSub   = false;
+
+    if(result_baseline == QUERY_STYLE_NOTHING || result_baseline == QUERY_STYLE_MULTIPLE_DIFFERENT ) {
+        // If not set or mixed, turn on superscript or subscript
+        if( prop == 0 ) {
+            setSuper = true;
+        } else {
+            setSub = true;
+        }
+    } else {
+        // Superscript
+        gboolean superscriptSet = (query->baseline_shift.set &&
+                                   query->baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+                                   query->baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+        // Subscript
+        gboolean subscriptSet = (query->baseline_shift.set &&
+                                 query->baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+                                 query->baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+        setSuper = !superscriptSet && prop == 0;
+        setSub   = !subscriptSet   && prop == 1;
+    }
+
+    // Set css properties
+    SPCSSAttr *css = sp_repr_css_attr_new ();
+    if( setSuper || setSub ) {
+        // Openoffice 2.3 and Adobe use 58%, Microsoft Word 2002 uses 65%.
+        sp_repr_css_set_property (css, "font-size", "58%");
+    } else {
+        sp_repr_css_set_property (css, "font-size", "");
+    }
+    if( setSuper ) {
+        sp_repr_css_set_property (css, "baseline-shift", "super");
+    } else if( setSub ) {
+        sp_repr_css_set_property (css, "baseline-shift", "sub");
+    } else {
+        sp_repr_css_set_property (css, "baseline-shift", "baseline");
+    }
+
+    // Apply css to selected objects.
+    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+    sp_desktop_set_style (desktop, css, true, true);
+
+    // Save for undo
+    sp_document_maybe_done (sp_desktop_document (SP_ACTIVE_DESKTOP), "ttb:script", SP_VERB_NONE,
+                                   _("Text: Change superscript or subscript"));
+
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
+}
+
 static void sp_text_align_mode_changed( EgeSelectOneAction *act, GObject *tbl )
 {
     // quit if run by the _changed callbacks
@@ -7090,6 +7166,8 @@ static void sp_text_toolbox_selection_changed(Inkscape::Selection */*selection*/
     int result_family   = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
     int result_style    = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
     int result_numbers  = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+    int result_baseline = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_BASELINES);
+
     // Used later:
     sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
 
@@ -7161,8 +7239,32 @@ static void sp_text_toolbox_selection_changed(Inkscape::Selection */*selection*/
         InkToggleAction* textItalicAction = INK_TOGGLE_ACTION( g_object_get_data( tbl, "TextItalicAction" ) );
         gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(textItalicAction), italicSet );
 
-        EgeSelectOneAction* textAlignAction = EGE_SELECT_ONE_ACTION( g_object_get_data( tbl, "TextAlignAction" ) );
+
+        // Superscript
+        gboolean superscriptSet =
+            ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) && 
+             query->baseline_shift.set &&
+             query->baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+             query->baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+        InkToggleAction* textSuperscriptAction = INK_TOGGLE_ACTION( g_object_get_data( tbl, "TextSuperscriptAction" ) );
+        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(textSuperscriptAction), superscriptSet );
+
+
+        // Subscript
+        gboolean subscriptSet =
+            ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) && 
+             query->baseline_shift.set &&
+             query->baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+             query->baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+        InkToggleAction* textSubscriptAction = INK_TOGGLE_ACTION( g_object_get_data( tbl, "TextSubscriptAction" ) );
+        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(textSubscriptAction), subscriptSet );
+
+
         // Alignment
+        EgeSelectOneAction* textAlignAction = EGE_SELECT_ONE_ACTION( g_object_get_data( tbl, "TextAlignAction" ) );
+
         // Note: SVG 1.1 doesn't include text-align, SVG 1.2 Tiny doesn't include text-align="justify"
         // text-align="justify" was a draft SVG 1.2 item (along with flowed text).
         // Only flowed text can be left and right justified at the same time.
@@ -7432,6 +7534,32 @@ static void sp_text_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions
         g_object_set_data( holder, "TextItalicAction", act );
     }
 
+    /* Style - Superscript */
+    {
+        InkToggleAction* act = ink_toggle_action_new( "TextSuperscriptAction",             // Name
+                                                      _("Toggle Superscript"),             // Label
+                                                      _("Toggle superscript"),             // Tooltip
+                                                      GTK_STOCK_ITALIC,                    // Icon (inkId)
+                                                      secondarySize );                     // Icon size
+        gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
+        g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_text_script_changed), holder );
+        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/text/super", false) );
+        g_object_set_data( holder, "TextSuperscriptAction", act );
+    }
+
+    /* Style - Subscript */
+    {
+        InkToggleAction* act = ink_toggle_action_new( "TextSubscriptAction",             // Name
+                                                      _("Toggle Subscript"),             // Label
+                                                      _("Toggle subscript"),             // Tooltip
+                                                      GTK_STOCK_ITALIC,                    // Icon (inkId)
+                                                      secondarySize );                     // Icon size
+        gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
+        g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(sp_text_script_changed), holder );
+        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/text/sub", false) );
+        g_object_set_data( holder, "TextSubscriptAction", act );
+    }
+
     /* Alignment */
     {
         GtkListStore* model = gtk_list_store_new( 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN );