Mercurial > octave
changeset 24076:1b7e49a72c62
improve text rendering in svg printout (bug #48567)
* text-renderer.h (text_renderer::string::family,
text_renderer::string::xdata): New data members to hold the actual
font family chosen by fontconfig and the x position of each
character. Provide accessor functions.
* ft-text-renderer.cc (ft_renderer::visit (text_element_string&),
ft_renderer::visit (text_element_symbol&)): Add family and xdata
imformation in the strlist.
* gl2ps-print.cc (gl2ps_renderer::strlist_to_svg): New private method
to write an svg text element from the data in an strlist.
(gl2ps_renderer::strlist_to_ps): Likewise, for ps output.
(gl2ps_renderer::render_text): For svg and epsformat, directly dump a
formatted text element in the gl2ps output.
(gl2ps_renderer::draw): When copying the output file to pipe, insert
manually a procedure in the prolog of eps files.
author | Pantxo Diribarne <pantxo.diribarne@gmail.com> |
---|---|
date | Tue, 11 Oct 2016 15:20:35 +0200 |
parents | 3645139bd28f |
children | e483dcb5777d |
files | libinterp/corefcn/ft-text-renderer.cc libinterp/corefcn/gl2ps-print.cc libinterp/corefcn/text-renderer.h |
diffstat | 3 files changed, 237 insertions(+), 89 deletions(-) [+] |
line wrap: on
line diff
--- a/libinterp/corefcn/ft-text-renderer.cc Tue Sep 05 17:38:51 2017 -0700 +++ b/libinterp/corefcn/ft-text-renderer.cc Tue Oct 11 15:20:35 2016 +0200 @@ -857,8 +857,9 @@ mbstate_t ps; memset (&ps, 0, sizeof (ps)); // Initialize state to 0. wchar_t wc; - + std::string fname = font.get_face ()->family_name; text_renderer::string fs (str, font, xoffset, yoffset); + std::vector<double> xdata; while (n > 0) { @@ -873,7 +874,7 @@ if (wc == L'\n') { - // Finish previous string in srtlist before processing + // Finish previous string in strlist before processing // the newline character fs.set_y (line_yoffset + yoffset); fs.set_color (color); @@ -881,9 +882,13 @@ if (! s.empty ()) { fs.set_string (s); + fs.set_xdata (xdata); + fs.set_family (fname); strlist.push_back (fs); } } + else + xdata.push_back (xoffset); glyph_index = process_character (wc, previous); @@ -892,9 +897,9 @@ previous = 0; // Start a new string in strlist idx = curr; + xdata.clear (); fs = text_renderer::string (str.substr (idx), font, line_xoffset, yoffset); - } else previous = glyph_index; @@ -913,6 +918,8 @@ { fs.set_y (line_yoffset + yoffset); fs.set_color (color); + fs.set_xdata (xdata); + fs.set_family (fname); strlist.push_back (fs); } } @@ -1056,12 +1063,14 @@ { uint32_t code = e.get_symbol_code (); + std::vector<double> xdata (1, xoffset); text_renderer::string fs ("-", font, xoffset, yoffset); if (code != text_element_symbol::invalid_code && font.is_valid ()) { process_character (code); fs.set_code (code); + fs.set_xdata (xdata); } else if (font.is_valid ()) ::warning ("ignoring unknown symbol: %d", e.get_symbol ()); @@ -1070,6 +1079,7 @@ { fs.set_y (line_yoffset + yoffset); fs.set_color (color); + fs.set_family (font.get_face ()->family_name); strlist.push_back (fs); } }
--- a/libinterp/corefcn/gl2ps-print.cc Tue Sep 05 17:38:51 2017 -0700 +++ b/libinterp/corefcn/gl2ps-print.cc Tue Oct 11 15:20:35 2016 +0200 @@ -188,11 +188,21 @@ private: // Use xform to compute the coordinates of the string list - // that have been parsed by freetype + // that have been parsed by freetype. void fix_strlist_position (double x, double y, double z, Matrix box, double rotation, std::list<text_renderer::string>& lst); + // Build an svg text element from a list of parsed strings. + std::string strlist_to_svg (double x, double y, double z, Matrix box, + double rotation, + std::list<octave::text_renderer::string>& lst); + + // Build a list of postscript commands from a list of parsed strings. + std::string strlist_to_ps (double x, double y, double z, Matrix box, + double rotation, + std::list<octave::text_renderer::string>& lst); + int alignment_to_mode (int ha, int va) const; FILE *fp; caseless_str term; @@ -361,11 +371,32 @@ char str[8192]; // 8 kB is a common kernel buffersize size_t nread, nwrite; nread = 1; + + // In EPS terminal read the header line by line and insert a + // new procedure + const char* fcn = "/SRX { gsave FCT moveto rotate xshow grestore } BD\n"; + bool header_found = ! (term.find ("eps") != std::string::npos); + while (! feof (tmpf) && nread) { - nread = std::fread (str, 1, 8192, tmpf); + if (! header_found && std::fgets (str, 8192, tmpf)) + nread = strlen (str); + else + nread = std::fread (str, 1, 8192, tmpf); + if (nread) { + if (! header_found && std::strncmp (str, "/SBCR", 5) == 0) + { + header_found = true; + nwrite = std::fwrite (fcn, 1, strlen (fcn), fp); + if (nwrite != strlen (fcn)) + { + octave::signal_handler (); + error ("gl2ps_renderer::draw: internal pipe error"); + } + } + nwrite = std::fwrite (str, 1, nread, fp); if (nwrite != nread) { @@ -432,14 +463,12 @@ coord_pix(1) -= (txtobj.get_y () + box(1))*cos (rot) + (txtobj.get_x () + box(0))*sin (rot); - // Turn coordinates back into current gl coordinates - ColumnVector coord = get_transform ().untransform (coord_pix(0), - coord_pix(1), - coord_pix(2), - false); - txtobj.set_x (coord(0)); - txtobj.set_y (coord(1)); - txtobj.set_z (coord(2)); + GLint vp[4]; + glGetIntegerv (GL_VIEWPORT, vp); + + txtobj.set_x (coord_pix(0)); + txtobj.set_y (vp[3] - coord_pix(1)); + txtobj.set_z (coord_pix(2)); } } } @@ -656,6 +685,158 @@ namespace octave { + std::string + gl2ps_renderer::strlist_to_svg (double x, double y, double z, + Matrix box, double rotation, + std::list<octave::text_renderer::string>& lst) + { + if (lst.empty ()) + return ""; + + //Use pixel coordinates to conform to gl2ps + ColumnVector coord_pix = get_transform ().transform (x, y, z, false); + + std::ostringstream os; + os << "<text xml:space=\"preserve\" "; + + // Rotation and translation are applied to the whole text element + os << "transform=\"" + << "translate(" << coord_pix(0) + box(0) << "," << coord_pix(1) - box(1) + << ") rotate(" << -rotation << "," << -box(0) << "," << box(1) + << ")\" "; + + // Use the first entry for the base text font + auto p = lst.begin (); + std::string name = p->get_family (); + std::string weight = p->get_weight (); + std::string angle = p->get_angle (); + double size = p->get_size (); + + os << "font-family=\"" << name << "\" " + << "font-weight=\"" << weight << "\" " + << "font-style=\"" << angle << "\" " + << "font-size=\"" << size << "\">"; + + + // build a tspan for each element in the strlist + for (p = lst.begin (); p != lst.end (); p++) + { + os << "<tspan "; + + if (name.compare (p->get_family ())) + os << "font-family=\"" << p->get_family () << "\" "; + + if (weight.compare (p->get_weight ())) + os << "font-weight=\"" << p->get_weight () << "\" "; + + if (angle.compare (p->get_angle ())) + os << "font-style=\"" << p->get_angle () << "\" "; + + if (size != p->get_size ()) + os << "font-size=\"" << p->get_size () << "\" "; + + os << "y=\"" << - p->get_y () << "\" "; + + Matrix col = p->get_color (); + os << "fill=\"rgb(" << col(0)*255 << "," + << col(1)*255 << "," << col(2)*255 << ")\" "; + + // provide an x coordinate for each character in the string + os << "x=\""; + std::vector<double> xdata = p->get_xdata (); + for (auto q = xdata.begin (); q != xdata.end (); q++) + os << (*q) << " "; + os << "\""; + + os << ">"; + + // translate unicode and special xml characters + if (p->get_code ()) + os << "&#" << p->get_code () << ";"; + else + { + const std::string str = p->get_string (); + for (auto q = str.begin (); q != str.end (); q++) + { + std::stringstream chr; + chr << *q; + if (chr.str () == "\"") + os << """; + else if (chr.str () == "'") + os << "'"; + else if (chr.str () == "&") + os << "&"; + else if (chr.str () == "<") + os << "<"; + else if (chr.str () == ">") + os << ">"; + else + os << chr.str (); + } + } + os << "</tspan>"; + } + os << "</text>"; + + return os.str (); + } + + std::string + gl2ps_renderer::strlist_to_ps (double x, double y, double z, + Matrix box, double rotation, + std::list<octave::text_renderer::string>& lst) + { + // Translate and rotate coordinates in order to use bottom-left alignment + fix_strlist_position (x, y, z, box, rotation, lst); + Matrix prev_color (1, 3, -1); + + std::ostringstream ss; + for (const auto& txtobj : lst) + { + // Color + if (txtobj.get_color () != prev_color) + { + prev_color = txtobj.get_color (); + for (int i = 0; i < 3; i++) + ss << prev_color(i) << " "; + + ss << "C\n"; + } + + // String + std::string str; + if (txtobj.get_code ()) + { + fontname = "Symbol"; + str = code_to_symbol (txtobj.get_code ()); + } + else + { + fontname = select_font (txtobj.get_name (), + txtobj.get_weight () == "bold", + txtobj.get_angle () == "italic"); + str = txtobj.get_string (); + } + + escape_character ("(", str); + escape_character (")", str); + + ss << "(" << str << ") ["; + + std::vector<double> xdata = txtobj.get_xdata (); + for (size_t i = 1; i < xdata.size (); i++) + ss << xdata[i] - xdata[i-1] << " "; + + ss << "10] " << rotation << " " << txtobj.get_x () + << " " << txtobj.get_y () << " " << txtobj.get_size () + << " /" << fontname << " SRX\n"; + } + + ss << "\n"; + + return ss.str (); + } + Matrix gl2ps_renderer::render_text (const std::string& txt, double x, double y, double z, @@ -666,89 +847,33 @@ if (txt.empty ()) return Matrix (1, 4, 0.0); - // We have no way to get a bounding box from gl2ps, so we parse the raw - // string using freetype Matrix bbox; std::string str = txt; std::list<text_renderer::string> lst; text_to_strlist (str, lst, bbox, ha, va, rotation); - - // When using "tex" or when the string has only one line and no - // special characters, use gl2ps for alignment - if (lst.empty () || term.find ("tex") != std::string::npos - || (lst.size () == 1 && ! lst.front ().get_code ())) - { - std::string name = fontname; - int sz = fontsize; - if (! lst.empty () && term.find ("tex") == std::string::npos) - { - text_renderer::string s = lst.front (); - name = select_font (s.get_name (), s.get_weight () == "bold", - s.get_angle () == "italic"); - set_color (s.get_color ()); - str = s.get_string (); - sz = s.get_size (); - } - - glRasterPos3d (x, y, z); - - // Escape parentheses until gl2ps does it (see bug #45301). - if (term.find ("svg") == std::string::npos - && term.find ("tex") == std::string::npos) - { - escape_character ("(", str); - escape_character (")", str); - } - - gl2psTextOpt (str.c_str (), name.c_str (), sz, - alignment_to_mode (ha, va), rotation); - return bbox; - } - - // Translate and rotate coordinates in order to use bottom-left alignment - fix_strlist_position (x, y, z, bbox, rotation, lst); + glRasterPos3d (x, y, z); - for (const auto& txtobj : lst) + // For svg/eps directly dump a preformated text element into gl2ps output + if (term.find ("svg") != std::string::npos) + { + std::string elt = strlist_to_svg (x, y, z, bbox, rotation, lst); + if (! elt.empty ()) + gl2psSpecial (GL2PS_SVG, elt.c_str ()); + } + else if (term.find ("eps") != std::string::npos) { - fontname = select_font (txtobj.get_name (), - txtobj.get_weight () == "bold", - txtobj.get_angle () == "italic"); - if (txtobj.get_code ()) - { - // This is only one character represented by a uint32 (utf8) code. - // We replace it by the corresponding character in the - // "Symbol" font except for svg which has built-in utf8 support. - if (term.find ("svg") == std::string::npos) - { - fontname = "Symbol"; - str = code_to_symbol (txtobj.get_code ()); - } - else - { - std::stringstream ss; - ss << txtobj.get_code (); - str = "&#" + ss.str () + ';'; - } - } - else - { - str = txtobj.get_string (); - // Escape parenthesis until gl2ps does it (see bug ##45301). - if (term.find ("svg") == std::string::npos) - { - escape_character ("(", str); - escape_character (")", str); - } - } + std::string elt = strlist_to_ps (x, y, z, bbox, rotation, lst); + if (! elt.empty ()) + gl2psSpecial (GL2PS_EPS, elt.c_str ()); - set_color (txtobj.get_color ()); - glRasterPos3d (txtobj.get_x (), txtobj.get_y (), txtobj.get_z ()); - gl2psTextOpt (str.c_str (), fontname.c_str (), txtobj.get_size (), - GL2PS_TEXT_BL, rotation); } + else + gl2psTextOpt (str.c_str (), fontname.c_str (), fontsize, + alignment_to_mode (ha, va), rotation); fontname = saved_font; + return bbox; }
--- a/libinterp/corefcn/text-renderer.h Tue Sep 05 17:38:51 2017 -0700 +++ b/libinterp/corefcn/text-renderer.h Tue Oct 11 15:20:35 2016 +0200 @@ -28,6 +28,7 @@ #include <list> #include <string> +#include <vector> #include "caseless-str.h" #include "dMatrix.h" @@ -129,13 +130,13 @@ public: string (const std::string& s, font& f, const double x0, const double y0) - : str (s), fnt (f), x (x0), y (y0), z (0.0), code (0), - color (Matrix (1,3,0.0)) + : str (s), family (f.get_name ()), fnt (f), x (x0), y (y0), z (0.0), + xdata (), code (0), color (Matrix (1,3,0.0)) { } string (const string& s) - : str (s.str), fnt (s.fnt), x (s.x), y (s.y), code (s.code), - color (s.color) + : str (s.str), family (s.family), fnt (s.fnt), x (s.x), y (s.y), + xdata (s.xdata), code (s.code), color (s.color) { } ~string (void) = default; @@ -145,9 +146,11 @@ if (&s != this) { str = s.str; + family = s.family; fnt = s.fnt; x = s.x; y = s.y; + xdata = s.xdata; code = s.code; color = s.color; } @@ -161,6 +164,10 @@ std::string get_name (void) const { return fnt.get_name (); } + std::string get_family (void) const { return family; } + + void set_family (const std::string& nm) { family = nm; } + std::string get_weight (void) const { return fnt.get_weight (); } std::string get_angle (void) const { return fnt.get_angle (); } @@ -171,6 +178,10 @@ double get_x (void) const { return x; } + void set_xdata (const std::vector<double>& x0) { xdata = x0; } + + std::vector<double> get_xdata (void) const { return xdata; } + void set_y (const double y0) { y = y0; } double get_y (void) const { return y; } @@ -195,8 +206,10 @@ private: std::string str; + std::string family; font fnt; double x, y, z; + std::vector<double> xdata; uint32_t code; Matrix color; };