Mercurial > octave
comparison libinterp/corefcn/gl2ps-print.cc @ 31607:aac27ad79be6 stable
maint: Re-indent code after switch to using namespace macros.
* build-env.h, build-env.in.cc, Cell.h, __betainc__.cc, __eigs__.cc,
__ftp__.cc, __ichol__.cc, __ilu__.cc, __isprimelarge__.cc, __magick_read__.cc,
__pchip_deriv__.cc, amd.cc, base-text-renderer.cc, base-text-renderer.h,
besselj.cc, bitfcns.cc, bsxfun.cc, c-file-ptr-stream.h, call-stack.cc,
call-stack.h, ccolamd.cc, cellfun.cc, chol.cc, colamd.cc, dasrt.cc, data.cc,
debug.cc, defaults.cc, defaults.h, det.cc, display.cc, display.h, dlmread.cc,
dynamic-ld.cc, dynamic-ld.h, ellipj.cc, environment.cc, environment.h,
error.cc, error.h, errwarn.h, event-manager.cc, event-manager.h,
event-queue.cc, event-queue.h, fcn-info.cc, fcn-info.h, fft.cc, fft2.cc,
file-io.cc, filter.cc, find.cc, ft-text-renderer.cc, ft-text-renderer.h,
gcd.cc, gl-render.cc, gl-render.h, gl2ps-print.cc, gl2ps-print.h,
graphics-toolkit.cc, graphics-toolkit.h, graphics.cc, gsvd.cc, gtk-manager.cc,
gtk-manager.h, help.cc, help.h, hook-fcn.cc, hook-fcn.h, input.cc, input.h,
interpreter-private.cc, interpreter-private.h, interpreter.cc, interpreter.h,
inv.cc, jsondecode.cc, jsonencode.cc, latex-text-renderer.cc,
latex-text-renderer.h, load-path.cc, load-path.h, load-save.cc, load-save.h,
lookup.cc, ls-hdf5.cc, ls-mat4.cc, ls-mat5.cc, lsode.cc, lu.cc, mappers.cc,
matrix_type.cc, max.cc, mex.cc, mexproto.h, mxarray.h, mxtypes.in.h,
oct-errno.in.cc, oct-hdf5-types.cc, oct-hist.cc, oct-hist.h, oct-map.cc,
oct-map.h, oct-opengl.h, oct-prcstrm.h, oct-process.cc, oct-process.h,
oct-stdstrm.h, oct-stream.cc, oct-stream.h, oct-strstrm.h,
octave-default-image.h, ordqz.cc, ordschur.cc, pager.cc, pager.h, pinv.cc,
pow2.cc, pr-output.cc, psi.cc, qr.cc, quadcc.cc, rand.cc, regexp.cc,
settings.cc, settings.h, sighandlers.cc, sighandlers.h, sparse-xpow.cc,
sqrtm.cc, stack-frame.cc, stack-frame.h, stream-euler.cc, strfns.cc, svd.cc,
syminfo.cc, syminfo.h, symrcm.cc, symrec.cc, symrec.h, symscope.cc, symscope.h,
symtab.cc, symtab.h, sysdep.cc, sysdep.h, text-engine.cc, text-engine.h,
text-renderer.cc, text-renderer.h, time.cc, toplev.cc, typecast.cc,
url-handle-manager.cc, url-handle-manager.h, urlwrite.cc, utils.cc, utils.h,
variables.cc, variables.h, xdiv.cc, __delaunayn__.cc, __init_fltk__.cc,
__init_gnuplot__.cc, __ode15__.cc, __voronoi__.cc, audioread.cc, convhulln.cc,
gzip.cc, cdef-class.cc, cdef-class.h, cdef-fwd.h, cdef-manager.cc,
cdef-manager.h, cdef-method.cc, cdef-method.h, cdef-object.cc, cdef-object.h,
cdef-package.cc, cdef-package.h, cdef-property.cc, cdef-property.h,
cdef-utils.cc, cdef-utils.h, ov-base-diag.cc, ov-base-int.cc, ov-base-mat.cc,
ov-base-mat.h, ov-base-scalar.cc, ov-base.cc, ov-base.h, ov-bool-mat.cc,
ov-bool-mat.h, ov-bool-sparse.cc, ov-bool.cc, ov-builtin.h, ov-cell.cc,
ov-ch-mat.cc, ov-class.cc, ov-class.h, ov-classdef.cc, ov-classdef.h,
ov-complex.cc, ov-cx-diag.cc, ov-cx-mat.cc, ov-cx-sparse.cc, ov-dld-fcn.cc,
ov-dld-fcn.h, ov-fcn-handle.cc, ov-fcn-handle.h, ov-fcn.h, ov-float.cc,
ov-flt-complex.cc, ov-flt-cx-diag.cc, ov-flt-cx-mat.cc, ov-flt-re-diag.cc,
ov-flt-re-mat.cc, ov-flt-re-mat.h, ov-intx.h, ov-java.cc, ov-lazy-idx.cc,
ov-legacy-range.cc, ov-magic-int.cc, ov-mex-fcn.cc, ov-mex-fcn.h,
ov-null-mat.cc, ov-perm.cc, ov-range.cc, ov-re-diag.cc, ov-re-mat.cc,
ov-re-mat.h, ov-re-sparse.cc, ov-scalar.cc, ov-str-mat.cc, ov-struct.cc,
ov-typeinfo.cc, ov-typeinfo.h, ov-usr-fcn.cc, ov-usr-fcn.h, ov.cc, ov.h, ovl.h,
octave.cc, octave.h, op-b-sbm.cc, op-bm-sbm.cc, op-cs-scm.cc, op-fm-fcm.cc,
op-fs-fcm.cc, op-s-scm.cc, op-scm-cs.cc, op-scm-s.cc, op-sm-cs.cc, ops.h,
anon-fcn-validator.cc, anon-fcn-validator.h, bp-table.cc, bp-table.h,
comment-list.cc, comment-list.h, filepos.h, lex.h, oct-lvalue.cc, oct-lvalue.h,
parse.h, profiler.cc, profiler.h, pt-anon-scopes.cc, pt-anon-scopes.h,
pt-arg-list.cc, pt-arg-list.h, pt-args-block.cc, pt-args-block.h,
pt-array-list.cc, pt-array-list.h, pt-assign.cc, pt-assign.h, pt-binop.cc,
pt-binop.h, pt-bp.cc, pt-bp.h, pt-cbinop.cc, pt-cbinop.h, pt-cell.cc,
pt-cell.h, pt-check.cc, pt-check.h, pt-classdef.cc, pt-classdef.h, pt-cmd.h,
pt-colon.cc, pt-colon.h, pt-const.cc, pt-const.h, pt-decl.cc, pt-decl.h,
pt-eval.cc, pt-eval.h, pt-except.cc, pt-except.h, pt-exp.cc, pt-exp.h,
pt-fcn-handle.cc, pt-fcn-handle.h, pt-id.cc, pt-id.h, pt-idx.cc, pt-idx.h,
pt-jump.h, pt-loop.cc, pt-loop.h, pt-mat.cc, pt-mat.h, pt-misc.cc, pt-misc.h,
pt-pr-code.cc, pt-pr-code.h, pt-select.cc, pt-select.h, pt-spmd.cc, pt-spmd.h,
pt-stmt.cc, pt-stmt.h, pt-tm-const.cc, pt-tm-const.h, pt-unop.cc, pt-unop.h,
pt-walk.cc, pt-walk.h, pt.cc, pt.h, token.cc, token.h, Range.cc, Range.h,
idx-vector.cc, idx-vector.h, range-fwd.h, CollocWt.cc, CollocWt.h,
aepbalance.cc, aepbalance.h, chol.cc, chol.h, gepbalance.cc, gepbalance.h,
gsvd.cc, gsvd.h, hess.cc, hess.h, lo-mappers.cc, lo-mappers.h, lo-specfun.cc,
lo-specfun.h, lu.cc, lu.h, oct-convn.cc, oct-convn.h, oct-fftw.cc, oct-fftw.h,
oct-norm.cc, oct-norm.h, oct-rand.cc, oct-rand.h, oct-spparms.cc,
oct-spparms.h, qr.cc, qr.h, qrp.cc, qrp.h, randgamma.cc, randgamma.h,
randmtzig.cc, randmtzig.h, randpoisson.cc, randpoisson.h, schur.cc, schur.h,
sparse-chol.cc, sparse-chol.h, sparse-lu.cc, sparse-lu.h, sparse-qr.cc,
sparse-qr.h, svd.cc, svd.h, child-list.cc, child-list.h, dir-ops.cc, dir-ops.h,
file-ops.cc, file-ops.h, file-stat.cc, file-stat.h, lo-sysdep.cc, lo-sysdep.h,
lo-sysinfo.cc, lo-sysinfo.h, mach-info.cc, mach-info.h, oct-env.cc, oct-env.h,
oct-group.cc, oct-group.h, oct-password.cc, oct-password.h, oct-syscalls.cc,
oct-syscalls.h, oct-time.cc, oct-time.h, oct-uname.cc, oct-uname.h,
action-container.cc, action-container.h, base-list.h, cmd-edit.cc, cmd-edit.h,
cmd-hist.cc, cmd-hist.h, f77-fcn.h, file-info.cc, file-info.h,
lo-array-errwarn.cc, lo-array-errwarn.h, lo-hash.cc, lo-hash.h, lo-ieee.h,
lo-regexp.cc, lo-regexp.h, lo-utils.cc, lo-utils.h, oct-base64.cc,
oct-base64.h, oct-glob.cc, oct-glob.h, oct-inttypes.h, oct-mutex.cc,
oct-mutex.h, oct-refcount.h, oct-shlib.cc, oct-shlib.h, oct-sparse.cc,
oct-sparse.h, oct-string.h, octave-preserve-stream-state.h, pathsearch.cc,
pathsearch.h, quit.cc, quit.h, unwind-prot.cc, unwind-prot.h, url-transfer.cc,
url-transfer.h:
Re-indent code after switch to using namespace macros.
author | Rik <rik@octave.org> |
---|---|
date | Thu, 01 Dec 2022 18:02:15 -0800 |
parents | e88a07dec498 |
children | a74935a6cc75 597f3ee61a48 |
comparison
equal
deleted
inserted
replaced
31605:e88a07dec498 | 31607:aac27ad79be6 |
---|---|
54 #include "sysdep.h" | 54 #include "sysdep.h" |
55 #include "text-renderer.h" | 55 #include "text-renderer.h" |
56 | 56 |
57 OCTAVE_BEGIN_NAMESPACE(octave) | 57 OCTAVE_BEGIN_NAMESPACE(octave) |
58 | 58 |
59 class | 59 class |
60 OCTINTERP_API | 60 OCTINTERP_API |
61 gl2ps_renderer : public opengl_renderer | 61 gl2ps_renderer : public opengl_renderer |
62 { | |
63 public: | |
64 | |
65 gl2ps_renderer (opengl_functions& glfcns, FILE *_fp, | |
66 const std::string& _term) | |
67 : opengl_renderer (glfcns), m_fp (_fp), m_term (_term), m_fontsize (), | |
68 m_fontname (), m_buffer_overflow (false), m_svg_def_index (0) | |
69 { } | |
70 | |
71 ~gl2ps_renderer (void) = default; | |
72 | |
73 // FIXME: should we import the functions from the base class and | |
74 // overload them here, or should we use a different name so we don't | |
75 // have to do this? Without the using declaration or a name change, | |
76 // the base class functions will be hidden. That may be OK, but it | |
77 // can also cause some confusion. | |
78 using opengl_renderer::draw; | |
79 | |
80 void draw (const graphics_object& go, const std::string& print_cmd); | |
81 | |
82 protected: | |
83 | |
84 Matrix render_text (const std::string& txt, | |
85 double x, double y, double z, | |
86 int halign, int valign, double rotation = 0.0); | |
87 | |
88 void set_font (const base_properties& props); | |
89 | |
90 static bool has_alpha (const graphics_handle& h) | |
62 { | 91 { |
63 public: | 92 bool retval = false; |
64 | |
65 gl2ps_renderer (opengl_functions& glfcns, FILE *_fp, | |
66 const std::string& _term) | |
67 : opengl_renderer (glfcns), m_fp (_fp), m_term (_term), m_fontsize (), | |
68 m_fontname (), m_buffer_overflow (false), m_svg_def_index (0) | |
69 { } | |
70 | |
71 ~gl2ps_renderer (void) = default; | |
72 | |
73 // FIXME: should we import the functions from the base class and | |
74 // overload them here, or should we use a different name so we don't | |
75 // have to do this? Without the using declaration or a name change, | |
76 // the base class functions will be hidden. That may be OK, but it | |
77 // can also cause some confusion. | |
78 using opengl_renderer::draw; | |
79 | |
80 void draw (const graphics_object& go, const std::string& print_cmd); | |
81 | |
82 protected: | |
83 | |
84 Matrix render_text (const std::string& txt, | |
85 double x, double y, double z, | |
86 int halign, int valign, double rotation = 0.0); | |
87 | |
88 void set_font (const base_properties& props); | |
89 | |
90 static bool has_alpha (const graphics_handle& h) | |
91 { | |
92 bool retval = false; | |
93 | |
94 gh_manager& gh_mgr = __get_gh_manager__ (); | |
95 | |
96 graphics_object go = gh_mgr.get_object (h); | |
97 | |
98 if (! go.valid_object ()) | |
99 return retval; | |
100 | |
101 if (go.isa ("axes") || go.isa ("hggroup")) | |
102 { | |
103 Matrix children = go.get ("children").matrix_value (); | |
104 for (octave_idx_type ii = 0; ii < children.numel (); ii++) | |
105 { | |
106 retval = has_alpha (graphics_handle (children(ii))); | |
107 if (retval) | |
108 break; | |
109 } | |
110 } | |
111 else if (go.isa ("patch") || go.isa ("surface")) | |
112 { | |
113 octave_value fa = go.get ("facealpha"); | |
114 if (fa.is_scalar_type () && fa.is_double_type () | |
115 && fa.double_value () < 1) | |
116 retval = true; | |
117 } | |
118 else if (go.isa ("scatter")) | |
119 { | |
120 octave_value fa = go.get ("markerfacealpha"); | |
121 if (fa.is_scalar_type () && fa.is_double_type () | |
122 && fa.double_value () < 1) | |
123 retval = true; | |
124 } | |
125 | |
126 return retval; | |
127 } | |
128 | |
129 void draw_axes (const axes::properties& props) | |
130 { | |
131 // Initialize a sorting tree (viewport) in gl2ps for each axes | |
132 GLint vp[4]; | |
133 m_glfcns.glGetIntegerv (GL_VIEWPORT, vp); | |
134 gl2psBeginViewport (vp); | |
135 | |
136 | |
137 // Don't remove hidden primitives when some of them are transparent | |
138 GLint opts; | |
139 gl2psGetOptions (&opts); | |
140 if (has_alpha (props.get___myhandle__ ())) | |
141 { | |
142 opts &= ~GL2PS_OCCLUSION_CULL; | |
143 // FIXME: currently the GL2PS_BLEND (which is more an equivalent of | |
144 // GL_ALPHA_TEST than GL_BLEND) is not working on a per primitive | |
145 // basis. We thus set it once per viewport. | |
146 gl2psEnable (GL2PS_BLEND); | |
147 } | |
148 else | |
149 { | |
150 opts |= GL2PS_OCCLUSION_CULL; | |
151 gl2psDisable (GL2PS_BLEND); | |
152 } | |
153 | |
154 gl2psSetOptions (opts); | |
155 | |
156 // Draw and finish () or there may be primitives missing in the gl2ps | |
157 // output. | |
158 opengl_renderer::draw_axes (props); | |
159 finish (); | |
160 | |
161 // Finalize viewport | |
162 GLint state = gl2psEndViewport (); | |
163 if (state == GL2PS_NO_FEEDBACK && props.is_visible ()) | |
164 warning ("gl2ps_renderer::draw_axes: empty feedback buffer and/or nothing else to print"); | |
165 else if (state == GL2PS_ERROR) | |
166 error ("gl2ps_renderer::draw_axes: gl2psEndPage returned GL2PS_ERROR"); | |
167 | |
168 m_buffer_overflow |= (state == GL2PS_OVERFLOW); | |
169 | |
170 // Don't draw background for subsequent viewports (legends, subplots, | |
171 // etc.) | |
172 gl2psGetOptions (&opts); | |
173 opts &= ~GL2PS_DRAW_BACKGROUND; | |
174 gl2psSetOptions (opts); | |
175 } | |
176 | |
177 void draw_text (const text::properties& props); | |
178 | |
179 void draw_image (const image::properties& props); | |
180 void draw_pixels (int w, int h, const float *data); | |
181 void draw_pixels (int w, int h, const uint8_t *data); | |
182 void draw_pixels (int w, int h, const uint16_t *data); | |
183 | |
184 void init_marker (const std::string& m, double size, float width) | |
185 { | |
186 opengl_renderer::init_marker (m, size, width); | |
187 | |
188 // FIXME: gl2ps can't handle closed contours so we set linecap/linejoin | |
189 // round to obtain a better looking result for some markers. | |
190 if (m == "o" || m == "v" || m == "^" || m == ">" || m == "<" || m == "h" | |
191 || m == "hexagram" || m == "p" || m == "pentagram") | |
192 { | |
193 set_linejoin ("round"); | |
194 set_linecap ("round"); | |
195 } | |
196 else | |
197 { | |
198 set_linejoin ("miter"); | |
199 set_linecap ("square"); | |
200 } | |
201 } | |
202 | |
203 void set_linestyle (const std::string& s, bool use_stipple = false, | |
204 double linewidth = 0.5) | |
205 { | |
206 opengl_renderer::set_linestyle (s, use_stipple, linewidth); | |
207 | |
208 if (s == "-" && ! use_stipple) | |
209 gl2psDisable (GL2PS_LINE_STIPPLE); | |
210 else | |
211 gl2psEnable (GL2PS_LINE_STIPPLE); | |
212 } | |
213 | |
214 void set_linecap (const std::string& s) | |
215 { | |
216 opengl_renderer::set_linejoin (s); | |
217 | |
218 #if defined (HAVE_GL2PSLINEJOIN) | |
219 if (s == "butt") | |
220 gl2psLineCap (GL2PS_LINE_CAP_BUTT); | |
221 else if (s == "square") | |
222 gl2psLineCap (GL2PS_LINE_CAP_SQUARE); | |
223 else if (s == "round") | |
224 gl2psLineCap (GL2PS_LINE_CAP_ROUND); | |
225 #endif | |
226 } | |
227 | |
228 void set_linejoin (const std::string& s) | |
229 { | |
230 opengl_renderer::set_linejoin (s); | |
231 | |
232 #if defined (HAVE_GL2PSLINEJOIN) | |
233 if (s == "round") | |
234 gl2psLineJoin (GL2PS_LINE_JOIN_ROUND); | |
235 else if (s == "miter") | |
236 gl2psLineJoin (GL2PS_LINE_JOIN_MITER); | |
237 else if (s == "chamfer") | |
238 gl2psLineJoin (GL2PS_LINE_JOIN_BEVEL); | |
239 #endif | |
240 } | |
241 | |
242 void set_polygon_offset (bool on, float offset = 0.0f) | |
243 { | |
244 if (on) | |
245 { | |
246 opengl_renderer::set_polygon_offset (on, offset); | |
247 gl2psEnable (GL2PS_POLYGON_OFFSET_FILL); | |
248 } | |
249 else | |
250 { | |
251 gl2psDisable (GL2PS_POLYGON_OFFSET_FILL); | |
252 opengl_renderer::set_polygon_offset (on, offset); | |
253 } | |
254 } | |
255 | |
256 void set_linewidth (float w) | |
257 { | |
258 gl2psLineWidth (w); | |
259 } | |
260 | |
261 private: | |
262 | |
263 // Use xform to compute the coordinates of the string list | |
264 // that have been parsed by freetype. | |
265 void fix_strlist_position (double x, double y, double z, | |
266 Matrix box, double rotation, | |
267 std::list<text_renderer::string>& lst); | |
268 | |
269 // Build an svg text element from a list of parsed strings | |
270 std::string format_svg_element (std::string str, Matrix bbox, | |
271 double rotation, ColumnVector coord_pix, | |
272 Matrix color); | |
273 | |
274 std::string strlist_to_svg (double x, double y, double z, Matrix box, | |
275 double rotation, | |
276 std::list<text_renderer::string>& lst); | |
277 | |
278 // Build a list of postscript commands from a list of parsed strings. | |
279 std::string strlist_to_ps (double x, double y, double z, Matrix box, | |
280 double rotation, | |
281 std::list<text_renderer::string>& lst); | |
282 | |
283 int alignment_to_mode (int ha, int va) const; | |
284 | |
285 FILE *m_fp; | |
286 caseless_str m_term; | |
287 double m_fontsize; | |
288 std::string m_fontname; | |
289 bool m_buffer_overflow; | |
290 std::size_t m_svg_def_index; | |
291 }; | |
292 | |
293 static bool | |
294 has_2D_axes (const graphics_handle& h) | |
295 { | |
296 bool retval = true; | |
297 | 93 |
298 gh_manager& gh_mgr = __get_gh_manager__ (); | 94 gh_manager& gh_mgr = __get_gh_manager__ (); |
299 | 95 |
300 graphics_object go = gh_mgr.get_object (h); | 96 graphics_object go = gh_mgr.get_object (h); |
301 | 97 |
302 if (! go.valid_object ()) | 98 if (! go.valid_object ()) |
303 return retval; | 99 return retval; |
304 | 100 |
305 if (go.isa ("figure") || go.isa ("uipanel")) | 101 if (go.isa ("axes") || go.isa ("hggroup")) |
306 { | 102 { |
307 Matrix children = go.get ("children").matrix_value (); | 103 Matrix children = go.get ("children").matrix_value (); |
308 for (octave_idx_type ii = 0; ii < children.numel (); ii++) | 104 for (octave_idx_type ii = 0; ii < children.numel (); ii++) |
309 { | 105 { |
310 retval = has_2D_axes (graphics_handle (children(ii))); | 106 retval = has_alpha (graphics_handle (children(ii))); |
311 if (! retval) | 107 if (retval) |
312 break; | 108 break; |
313 } | 109 } |
314 } | 110 } |
315 else if (go.isa ("axes")) | 111 else if (go.isa ("patch") || go.isa ("surface")) |
316 { | 112 { |
317 axes::properties& ap | 113 octave_value fa = go.get ("facealpha"); |
318 = reinterpret_cast<axes::properties&> (go.get_properties ()); | 114 if (fa.is_scalar_type () && fa.is_double_type () |
319 retval = ap.get_is2D (true); | 115 && fa.double_value () < 1) |
116 retval = true; | |
117 } | |
118 else if (go.isa ("scatter")) | |
119 { | |
120 octave_value fa = go.get ("markerfacealpha"); | |
121 if (fa.is_scalar_type () && fa.is_double_type () | |
122 && fa.double_value () < 1) | |
123 retval = true; | |
320 } | 124 } |
321 | 125 |
322 return retval; | 126 return retval; |
323 } | 127 } |
324 | 128 |
325 static std::string | 129 void draw_axes (const axes::properties& props) |
326 get_title (const graphics_handle& h) | |
327 { | 130 { |
328 std::string retval; | 131 // Initialize a sorting tree (viewport) in gl2ps for each axes |
329 | 132 GLint vp[4]; |
330 gh_manager& gh_mgr = __get_gh_manager__ (); | 133 m_glfcns.glGetIntegerv (GL_VIEWPORT, vp); |
331 | 134 gl2psBeginViewport (vp); |
332 graphics_object go = gh_mgr.get_object (h); | 135 |
333 | 136 |
334 if (! go.valid_object ()) | 137 // Don't remove hidden primitives when some of them are transparent |
335 return retval; | 138 GLint opts; |
336 | 139 gl2psGetOptions (&opts); |
337 if (go.isa ("figure")) | 140 if (has_alpha (props.get___myhandle__ ())) |
338 { | 141 { |
339 figure::properties& fp | 142 opts &= ~GL2PS_OCCLUSION_CULL; |
340 = reinterpret_cast<figure::properties&> (go.get_properties ()); | 143 // FIXME: currently the GL2PS_BLEND (which is more an equivalent of |
341 | 144 // GL_ALPHA_TEST than GL_BLEND) is not working on a per primitive |
342 retval = fp.get_title (); | 145 // basis. We thus set it once per viewport. |
343 } | 146 gl2psEnable (GL2PS_BLEND); |
344 | |
345 return retval; | |
346 } | |
347 | |
348 void | |
349 gl2ps_renderer::draw (const graphics_object& go, const std::string& print_cmd) | |
350 { | |
351 static bool in_draw = false; | |
352 static std::string old_print_cmd; | |
353 static GLint buffsize; | |
354 | |
355 if (! in_draw) | |
356 { | |
357 unwind_protect frame; | |
358 | |
359 frame.protect_var (in_draw); | |
360 | |
361 in_draw = true; | |
362 | |
363 GLint gl2ps_term = GL2PS_PS; | |
364 if (m_term.find ("eps") != std::string::npos) | |
365 gl2ps_term = GL2PS_EPS; | |
366 else if (m_term.find ("pdf") != std::string::npos) | |
367 gl2ps_term = GL2PS_PDF; | |
368 else if (m_term.find ("ps") != std::string::npos) | |
369 gl2ps_term = GL2PS_PS; | |
370 else if (m_term.find ("svg") != std::string::npos) | |
371 gl2ps_term = GL2PS_SVG; | |
372 else if (m_term.find ("pgf") != std::string::npos) | |
373 gl2ps_term = GL2PS_PGF; | |
374 else if (m_term.find ("tex") != std::string::npos) | |
375 gl2ps_term = GL2PS_TEX; | |
376 else | |
377 warning ("gl2ps_renderer::draw: Unknown terminal %s, using 'ps'", | |
378 m_term.c_str ()); | |
379 | |
380 GLint gl2ps_text = 0; | |
381 if (m_term.find ("notxt") != std::string::npos) | |
382 gl2ps_text = GL2PS_NO_TEXT; | |
383 | |
384 // Find Title for plot | |
385 const graphics_handle& myhandle = go.get ("__myhandle__"); | |
386 std::string plot_title = get_title (myhandle); | |
387 if (plot_title.empty ()) | |
388 plot_title = "Octave plot"; | |
389 | |
390 // Default sort order optimizes for 3D plots | |
391 GLint gl2ps_sort = GL2PS_BSP_SORT; | |
392 | |
393 // FIXME: gl2ps does not provide a way to change the sorting algorithm | |
394 // on a viewport basis, we thus disable sorting only if all axes are 2D | |
395 if (has_2D_axes (myhandle)) | |
396 gl2ps_sort = GL2PS_NO_SORT; | |
397 | |
398 // Use a temporary file in case an overflow happens | |
399 std::string tmpfile (sys::tempnam (sys::env::get_temp_directory (), | |
400 "oct-")); | |
401 FILE *tmpf = sys::fopen_tmp (tmpfile, "w+b"); | |
402 | |
403 if (! tmpf) | |
404 error ("gl2ps_renderer::draw: couldn't open temporary file for printing"); | |
405 | |
406 frame.add ([=] () { std::fclose (tmpf); }); | |
407 | |
408 // Reset buffsize, unless this is 2nd pass of a texstandalone print. | |
409 if (m_term.find ("tex") == std::string::npos) | |
410 buffsize = 2*1024*1024; | |
411 else | |
412 buffsize /= 2; | |
413 | |
414 m_buffer_overflow = true; | |
415 | |
416 while (m_buffer_overflow) | |
417 { | |
418 m_buffer_overflow = false; | |
419 buffsize *= 2; | |
420 | |
421 std::fseek (tmpf, 0, SEEK_SET); | |
422 octave_ftruncate_wrapper (fileno (tmpf), 0); | |
423 | |
424 // For LaTeX output the print process uses 2 drawnow() commands. | |
425 // The first one is for the pdf/ps/eps graph to be included. The | |
426 // print_cmd is saved as old_print_cmd. Then the second drawnow() | |
427 // outputs the tex-file and the graphic filename to be included is | |
428 // extracted from old_print_cmd. | |
429 | |
430 std::string include_graph; | |
431 | |
432 std::size_t found_redirect = old_print_cmd.find ('>'); | |
433 | |
434 if (found_redirect != std::string::npos) | |
435 include_graph = old_print_cmd.substr (found_redirect + 1); | |
436 else | |
437 include_graph = old_print_cmd; | |
438 | |
439 std::size_t n_begin = include_graph.find_first_not_of (R"( "')"); | |
440 | |
441 if (n_begin != std::string::npos) | |
442 { | |
443 // Strip any quote characters characters around filename | |
444 std::size_t n_end = include_graph.find_last_not_of (R"( "')"); | |
445 include_graph = include_graph.substr (n_begin, | |
446 n_end - n_begin + 1); | |
447 // Strip path from filename | |
448 n_begin = include_graph.find_last_of (sys::file_ops::dir_sep_chars ()); | |
449 include_graph = include_graph.substr (n_begin + 1); | |
450 } | |
451 else | |
452 include_graph = "foobar-inc"; | |
453 | |
454 // FIXME: workaround gl2ps drawing 2 background planes, the first | |
455 // eventually being black and producing visual artifacts | |
456 const figure::properties& fprop | |
457 = dynamic_cast<const figure::properties&> (go.get_properties ()); | |
458 Matrix c = fprop.get_color_rgb (); | |
459 m_glfcns.glClearColor (c(0), c(1), c(2), 1); | |
460 | |
461 // Allow figures to be printed at arbitrary resolution | |
462 set_device_pixel_ratio (fprop.get___device_pixel_ratio__ ()); | |
463 | |
464 // GL2PS_SILENT was removed to allow gl2ps to print errors on stderr | |
465 GLint ret = gl2psBeginPage (plot_title.c_str (), "Octave", | |
466 nullptr, gl2ps_term, gl2ps_sort, | |
467 (GL2PS_BEST_ROOT | |
468 | gl2ps_text | |
469 | GL2PS_DRAW_BACKGROUND | |
470 | GL2PS_NO_PS3_SHADING | |
471 | GL2PS_USE_CURRENT_VIEWPORT), | |
472 GL_RGBA, 0, nullptr, 0, 0, 0, | |
473 buffsize, tmpf, include_graph.c_str ()); | |
474 if (ret == GL2PS_ERROR) | |
475 { | |
476 old_print_cmd.clear (); | |
477 error ("gl2ps_renderer::draw: gl2psBeginPage returned GL2PS_ERROR"); | |
478 } | |
479 | |
480 opengl_renderer::draw (go); | |
481 | |
482 if (m_buffer_overflow) | |
483 warning ("gl2ps_renderer::draw: retrying with buffer size: %.1E B\n", double (2*buffsize)); | |
484 | |
485 if (! m_buffer_overflow) | |
486 old_print_cmd = print_cmd; | |
487 | |
488 // Don't check return value of gl2psEndPage, it is not meaningful. | |
489 // Errors and warnings are checked after gl2psEndViewport in | |
490 // gl2ps_renderer::draw_axes instead. | |
491 gl2psEndPage (); | |
492 } | |
493 | |
494 // Copy temporary file to pipe | |
495 std::fseek (tmpf, 0, SEEK_SET); | |
496 char str[8192]; // 8 kB is a common kernel buffersize | |
497 std::size_t nread, nwrite; | |
498 nread = 1; | |
499 | |
500 // In EPS terminal read the header line by line and insert a | |
501 // new procedure | |
502 const char *fcn = "/SRX { gsave FCT moveto rotate xshow grestore } BD\n"; | |
503 bool header_found = ! (m_term.find ("eps") != std::string::npos | |
504 || m_term.find ("svg") != std::string::npos); | |
505 | |
506 while (! feof (tmpf) && nread) | |
507 { | |
508 if (! header_found && std::fgets (str, 8192, tmpf)) | |
509 nread = strlen (str); | |
510 else | |
511 nread = std::fread (str, 1, 8192, tmpf); | |
512 | |
513 if (nread) | |
514 { | |
515 if (! header_found && std::strncmp (str, "/SBCR", 5) == 0) | |
516 { | |
517 header_found = true; | |
518 nwrite = std::fwrite (fcn, 1, strlen (fcn), m_fp); | |
519 if (nwrite != strlen (fcn)) | |
520 { | |
521 // FIXME: is this the best thing to do here? | |
522 respond_to_pending_signals (); | |
523 error ("gl2ps_renderer::draw: internal pipe error"); | |
524 } | |
525 } | |
526 else if (m_term.find ("svg") != std::string::npos) | |
527 { | |
528 // FIXME: gl2ps uses pixel units for SVG format. | |
529 // Modify resulting svg to use points instead. | |
530 // Remove this "else if" block, and | |
531 // make header_found true for SVG if gl2ps is fixed. | |
532 | |
533 // Specify number of characters because STR may have | |
534 // come from std::fread and not end with a NUL | |
535 // character. | |
536 std::string srchstr (str, nread); | |
537 std::size_t pos = srchstr.find ("<svg "); | |
538 if (! header_found && pos != std::string::npos) | |
539 { | |
540 header_found = true; | |
541 pos = srchstr.find ("px"); | |
542 if (pos != std::string::npos) | |
543 { | |
544 srchstr[pos+1] = 't'; // "px" -> "pt" | |
545 // Assume the second occurrence is at the same line | |
546 pos = srchstr.find ("px", pos); | |
547 srchstr[pos+1] = 't'; // "px" -> "pt" | |
548 std::strcpy (str, srchstr.c_str ()); | |
549 } | |
550 } | |
551 } | |
552 | |
553 nwrite = std::fwrite (str, 1, nread, m_fp); | |
554 if (nwrite != nread) | |
555 { | |
556 // FIXME: is this the best thing to do here? | |
557 respond_to_pending_signals (); // Clear SIGPIPE signal | |
558 error ("gl2ps_renderer::draw: internal pipe error"); | |
559 } | |
560 } | |
561 } | |
562 } | |
563 else | |
564 opengl_renderer::draw (go); | |
565 } | |
566 | |
567 int | |
568 gl2ps_renderer::alignment_to_mode (int ha, int va) const | |
569 { | |
570 int gl2psa = GL2PS_TEXT_BL; | |
571 | |
572 if (ha == 0) | |
573 { | |
574 if (va == 0 || va == 3) | |
575 gl2psa=GL2PS_TEXT_BL; | |
576 else if (va == 2) | |
577 gl2psa=GL2PS_TEXT_TL; | |
578 else if (va == 1) | |
579 gl2psa=GL2PS_TEXT_CL; | |
580 } | |
581 else if (ha == 2) | |
582 { | |
583 if (va == 0 || va == 3) | |
584 gl2psa=GL2PS_TEXT_BR; | |
585 else if (va == 2) | |
586 gl2psa=GL2PS_TEXT_TR; | |
587 else if (va == 1) | |
588 gl2psa=GL2PS_TEXT_CR; | |
589 } | |
590 else if (ha == 1) | |
591 { | |
592 if (va == 0 || va == 3) | |
593 gl2psa=GL2PS_TEXT_B; | |
594 else if (va == 2) | |
595 gl2psa=GL2PS_TEXT_T; | |
596 else if (va == 1) | |
597 gl2psa=GL2PS_TEXT_C; | |
598 } | |
599 | |
600 return gl2psa; | |
601 } | |
602 | |
603 void | |
604 gl2ps_renderer::fix_strlist_position (double x, double y, double z, | |
605 Matrix box, double rotation, | |
606 std::list<text_renderer::string>& lst) | |
607 { | |
608 for (auto& txtobj : lst) | |
609 { | |
610 // Get pixel coordinates | |
611 ColumnVector coord_pix = get_transform ().transform (x, y, z, false); | |
612 | |
613 // Translate and rotate | |
614 double rot = rotation * 4.0 * atan (1.0) / 180; | |
615 coord_pix(0) += (txtobj.get_x () + box(0))*cos (rot) | |
616 - (txtobj.get_y () + box(1))*sin (rot); | |
617 coord_pix(1) -= (txtobj.get_y () + box(1))*cos (rot) | |
618 + (txtobj.get_x () + box(0))*sin (rot); | |
619 | |
620 GLint vp[4]; | |
621 m_glfcns.glGetIntegerv (GL_VIEWPORT, vp); | |
622 | |
623 txtobj.set_x (coord_pix(0)); | |
624 txtobj.set_y (vp[3] - coord_pix(1)); | |
625 txtobj.set_z (coord_pix(2)); | |
626 } | |
627 } | |
628 | |
629 static std::string | |
630 code_to_symbol (uint32_t code) | |
631 { | |
632 std::string retval; | |
633 | |
634 uint32_t idx = code - 945; | |
635 if (idx < 25) | |
636 { | |
637 std::string characters ("abgdezhqiklmnxoprVstufcyw"); | |
638 retval = characters[idx]; | |
639 return retval; | |
640 } | |
641 | |
642 idx = code - 913; | |
643 if (idx < 25) | |
644 { | |
645 std::string characters ("ABGDEZHQIKLMNXOPRVSTUFCYW"); | |
646 retval = characters[idx]; | |
647 } | |
648 else if (code == 978) | |
649 retval = "U"; | |
650 else if (code == 215) | |
651 retval = "\xb4"; | |
652 else if (code == 177) | |
653 retval = "\xb1"; | |
654 else if (code == 8501) | |
655 retval = "\xc0"; | |
656 else if (code == 8465) | |
657 retval = "\xc1"; | |
658 else if (code == 8242) | |
659 retval = "\xa2"; | |
660 else if (code == 8736) | |
661 retval = "\xd0"; | |
662 else if (code == 172) | |
663 retval = "\xd8"; | |
664 else if (code == 9829) | |
665 retval = "\xa9"; | |
666 else if (code == 8472) | |
667 retval = "\xc3"; | |
668 else if (code == 8706) | |
669 retval = "\xb6"; | |
670 else if (code == 8704) | |
671 retval = "\x22"; | |
672 else if (code == 9827) | |
673 retval = "\xa7"; | |
674 else if (code == 9824) | |
675 retval = "\xaa"; | |
676 else if (code == 8476) | |
677 retval = "\xc2"; | |
678 else if (code == 8734) | |
679 retval = "\xa5"; | |
680 else if (code == 8730) | |
681 retval = "\xd6"; | |
682 else if (code == 8707) | |
683 retval = "\x24"; | |
684 else if (code == 9830) | |
685 retval = "\xa8"; | |
686 else if (code == 8747) | |
687 retval = "\xf2"; | |
688 else if (code == 8727) | |
689 retval = "\x2a"; | |
690 else if (code == 8744) | |
691 retval = "\xda"; | |
692 else if (code == 8855) | |
693 retval = "\xc4"; | |
694 else if (code == 8901) | |
695 retval = "\xd7"; | |
696 else if (code == 8728) | |
697 retval = "\xb0"; | |
698 else if (code == 8745) | |
699 retval = "\xc7"; | |
700 else if (code == 8743) | |
701 retval = "\xd9"; | |
702 else if (code == 8856) | |
703 retval = "\xc6"; | |
704 else if (code == 8729) | |
705 retval = "\xb7"; | |
706 else if (code == 8746) | |
707 retval = "\xc8"; | |
708 else if (code == 8853) | |
709 retval = "\xc5"; | |
710 else if (code == 8804) | |
711 retval = "\xa3"; | |
712 else if (code == 8712) | |
713 retval = "\xce"; | |
714 else if (code == 8839) | |
715 retval = "\xca"; | |
716 else if (code == 8801) | |
717 retval = "\xba"; | |
718 else if (code == 8773) | |
719 retval = "\x40"; | |
720 else if (code == 8834) | |
721 retval = "\xcc"; | |
722 else if (code == 8805) | |
723 retval = "\xb3"; | |
724 else if (code == 8715) | |
725 retval = "\x27"; | |
726 else if (code == 8764) | |
727 retval = "\x7e"; | |
728 else if (code == 8733) | |
729 retval = "\xb5"; | |
730 else if (code == 8838) | |
731 retval = "\xcd"; | |
732 else if (code == 8835) | |
733 retval = "\xc9"; | |
734 else if (code == 8739) | |
735 retval = "\xbd"; | |
736 else if (code == 8776) | |
737 retval = "\xbb"; | |
738 else if (code == 8869) | |
739 retval = "\x5e"; | |
740 else if (code == 8656) | |
741 retval = "\xdc"; | |
742 else if (code == 8592) | |
743 retval = "\xac"; | |
744 else if (code == 8658) | |
745 retval = "\xde"; | |
746 else if (code == 8594) | |
747 retval = "\xae"; | |
748 else if (code == 8596) | |
749 retval = "\xab"; | |
750 else if (code == 8593) | |
751 retval = "\xad"; | |
752 else if (code == 8595) | |
753 retval = "\xaf"; | |
754 else if (code == 8970) | |
755 retval = "\xeb"; | |
756 else if (code == 8971) | |
757 retval = "\xfb"; | |
758 else if (code == 10216) | |
759 retval = "\xe1"; | |
760 else if (code == 10217) | |
761 retval = "\xf1"; | |
762 else if (code == 8968) | |
763 retval = "\xe9"; | |
764 else if (code == 8969) | |
765 retval = "\xf9"; | |
766 else if (code == 8800) | |
767 retval = "\xb9"; | |
768 else if (code == 8230) | |
769 retval = "\xbc"; | |
770 else if (code == 176) | |
771 retval = "\xb0"; | |
772 else if (code == 8709) | |
773 retval = "\xc6"; | |
774 else if (code == 169) | |
775 retval = "\xd3"; | |
776 | |
777 if (retval.empty ()) | |
778 warning ("print: unhandled symbol %d", code); | |
779 | |
780 return retval; | |
781 } | |
782 | |
783 static std::string | |
784 select_font (caseless_str fn, bool isbold, bool isitalic) | |
785 { | |
786 std::transform (fn.begin (), fn.end (), fn.begin (), ::tolower); | |
787 std::string fontname; | |
788 if (fn == "times" || fn == "times-roman") | |
789 { | |
790 if (isitalic && isbold) | |
791 fontname = "Times-BoldItalic"; | |
792 else if (isitalic) | |
793 fontname = "Times-Italic"; | |
794 else if (isbold) | |
795 fontname = "Times-Bold"; | |
796 else | |
797 fontname = "Times-Roman"; | |
798 } | |
799 else if (fn == "courier") | |
800 { | |
801 if (isitalic && isbold) | |
802 fontname = "Courier-BoldOblique"; | |
803 else if (isitalic) | |
804 fontname = "Courier-Oblique"; | |
805 else if (isbold) | |
806 fontname = "Courier-Bold"; | |
807 else | |
808 fontname = "Courier"; | |
809 } | |
810 else if (fn == "symbol") | |
811 fontname = "Symbol"; | |
812 else if (fn == "zapfdingbats") | |
813 fontname = "ZapfDingbats"; | |
814 else | |
815 { | |
816 if (isitalic && isbold) | |
817 fontname = "Helvetica-BoldOblique"; | |
818 else if (isitalic) | |
819 fontname = "Helvetica-Oblique"; | |
820 else if (isbold) | |
821 fontname = "Helvetica-Bold"; | |
822 else | |
823 fontname = "Helvetica"; | |
824 } | |
825 return fontname; | |
826 } | |
827 | |
828 static void | |
829 escape_character (const std::string chr, std::string& str) | |
830 { | |
831 std::size_t idx = str.find (chr); | |
832 while (idx != std::string::npos) | |
833 { | |
834 str.insert (idx, 1, '\\'); | |
835 idx = str.find (chr, idx + 2); | |
836 } | |
837 } | |
838 | |
839 std::string | |
840 gl2ps_renderer::format_svg_element (std::string str, Matrix box, | |
841 double rotation, ColumnVector coord_pix, | |
842 Matrix color) | |
843 { | |
844 // Extract <defs> elements and change their id to avoid conflict with | |
845 // defs coming from another svg string | |
846 std::string::size_type n1 = str.find ("<defs>"); | |
847 if (n1 == std::string::npos) | |
848 return std::string (); | |
849 | |
850 std::string id, new_id; | |
851 n1 = str.find ("<path", ++n1); | |
852 std::string::size_type n2; | |
853 | |
854 while (n1 != std::string::npos) | |
855 { | |
856 // Extract the identifier id='identifier' | |
857 n1 = str.find ("id='", n1) + 4; | |
858 n2 = str.find ("'", n1); | |
859 id = str.substr (n1, n2-n1); | |
860 | |
861 new_id = std::to_string (m_svg_def_index) + "-" + id ; | |
862 | |
863 str.replace (n1, n2-n1, new_id); | |
864 | |
865 std::string::size_type n_ref = str.find ("#" + id); | |
866 | |
867 while (n_ref != std::string::npos) | |
868 { | |
869 str.replace (n_ref + 1, id.length (), new_id); | |
870 n_ref = str.find ("#" + id); | |
871 } | |
872 | |
873 n1 = str.find ("<path", n1); | |
874 } | |
875 | |
876 m_svg_def_index++; | |
877 | |
878 n1 = str.find ("<defs>"); | |
879 n2 = str.find ("</defs>") + 7; | |
880 | |
881 std::string defs = str.substr (n1, n2-n1); | |
882 | |
883 // Extract the group containing the <use> elements and transform its | |
884 // coordinates using the bbox and coordinates info. | |
885 | |
886 // Extract the original viewBox anchor | |
887 n1 = str.find ("viewBox='") + 9; | |
888 if (n1 == std::string::npos) | |
889 return std::string (); | |
890 | |
891 n2 = str.find (" ", n1); | |
892 double original_x0 = std::stod (str.substr (n1, n2-n1)); | |
893 | |
894 n1 = n2+1; | |
895 n2 = str.find (" ", n1); | |
896 double original_y0 = std::stod (str.substr (n1, n2-n1)); | |
897 | |
898 // First look for local transform in the original svg | |
899 std::string orig_trans; | |
900 n1 = str.find ("<g id='page1' transform='"); | |
901 if (n1 != std::string::npos) | |
902 { | |
903 n1 += 25; | |
904 n2 = str.find ("'", n1); | |
905 orig_trans = str.substr (n1, n2-n1); | |
906 n1 = n2 + 1; | |
907 } | 147 } |
908 else | 148 else |
909 { | 149 { |
910 n1 = str.find ("<g id='page1'"); | 150 opts |= GL2PS_OCCLUSION_CULL; |
911 n1 += 13; | 151 gl2psDisable (GL2PS_BLEND); |
912 } | 152 } |
913 | 153 |
914 n2 = str.find ("</g>", n1) + 4; | 154 gl2psSetOptions (opts); |
915 | 155 |
916 // The first applied transformation is the right-most | 156 // Draw and finish () or there may be primitives missing in the gl2ps |
917 // 1* Apply original transform | 157 // output. |
918 std::string tform = orig_trans; | 158 opengl_renderer::draw_axes (props); |
919 | 159 finish (); |
920 // 2* Move the anchor to the final position | 160 |
921 tform = std::string ("translate") | 161 // Finalize viewport |
922 + "(" + std::to_string (box(0) - original_x0 + coord_pix(0)) | 162 GLint state = gl2psEndViewport (); |
923 + "," + std::to_string (-(box(3) + box(1)) - original_y0 + coord_pix(1)) | 163 if (state == GL2PS_NO_FEEDBACK && props.is_visible ()) |
924 + ") " + tform; | 164 warning ("gl2ps_renderer::draw_axes: empty feedback buffer and/or nothing else to print"); |
925 | 165 else if (state == GL2PS_ERROR) |
926 // 3* Rotate around the final position | 166 error ("gl2ps_renderer::draw_axes: gl2psEndPage returned GL2PS_ERROR"); |
927 if (rotation != 0) | 167 |
928 tform = std::string ("rotate") | 168 m_buffer_overflow |= (state == GL2PS_OVERFLOW); |
929 + "(" + std::to_string (-rotation) | 169 |
930 + "," + std::to_string (coord_pix(0)) | 170 // Don't draw background for subsequent viewports (legends, subplots, |
931 + "," + std::to_string (coord_pix(1)) | 171 // etc.) |
932 + ") " + tform; | 172 gl2psGetOptions (&opts); |
933 | 173 opts &= ~GL2PS_DRAW_BACKGROUND; |
934 // Fill color | 174 gl2psSetOptions (opts); |
935 std::string fill = "fill='rgb(" | |
936 + std::to_string (static_cast<uint8_t> (color(0) * 255.0)) + "," | |
937 + std::to_string (static_cast<uint8_t> (color(1) * 255.0)) + "," | |
938 + std::to_string (static_cast<uint8_t> (color(2) * 255.0)) + ")' "; | |
939 | |
940 std::string use_group = "<g " | |
941 + fill | |
942 + "transform='" + tform + "'" | |
943 + str.substr (n1, n2-n1); | |
944 | |
945 return defs + "\n" + use_group; | |
946 } | 175 } |
947 | 176 |
948 std::string | 177 void draw_text (const text::properties& props); |
949 gl2ps_renderer::strlist_to_svg (double x, double y, double z, | 178 |
950 Matrix box, double rotation, | 179 void draw_image (const image::properties& props); |
951 std::list<text_renderer::string>& lst) | 180 void draw_pixels (int w, int h, const float *data); |
181 void draw_pixels (int w, int h, const uint8_t *data); | |
182 void draw_pixels (int w, int h, const uint16_t *data); | |
183 | |
184 void init_marker (const std::string& m, double size, float width) | |
952 { | 185 { |
953 //Use pixel coordinates to conform to gl2ps | 186 opengl_renderer::init_marker (m, size, width); |
954 ColumnVector coord_pix = get_transform ().transform (x, y, z, false); | 187 |
955 | 188 // FIXME: gl2ps can't handle closed contours so we set linecap/linejoin |
956 if (lst.empty ()) | 189 // round to obtain a better looking result for some markers. |
957 return ""; | 190 if (m == "o" || m == "v" || m == "^" || m == ">" || m == "<" || m == "h" |
958 | 191 || m == "hexagram" || m == "p" || m == "pentagram") |
959 // This may already be an svg image. | |
960 std::string svg = lst.front ().get_svg_element (); | |
961 if (! svg.empty ()) | |
962 return format_svg_element (svg, box, rotation, coord_pix, | |
963 lst.front ().get_color ()); | |
964 | |
965 // Rotation and translation are applied to the whole group | |
966 std::ostringstream os; | |
967 os << R"(<g xml:space="preserve" )"; | |
968 os << "transform=\"" | |
969 << "translate(" << coord_pix(0) + box(0) << "," << coord_pix(1) - box(1) | |
970 << ") rotate(" << -rotation << "," << -box(0) << "," << box(1) | |
971 << ")\" "; | |
972 | |
973 // Use the first entry for the base text font | |
974 auto p = lst.begin (); | |
975 std::string name = p->get_family (); | |
976 std::string weight = p->get_weight (); | |
977 std::string angle = p->get_angle (); | |
978 double size = p->get_size (); | |
979 | |
980 os << "font-family=\"" << name << "\" " | |
981 << "font-weight=\"" << weight << "\" " | |
982 << "font-style=\"" << angle << "\" " | |
983 << "font-size=\"" << size << "\">"; | |
984 | |
985 | |
986 // Build a text element for each element in the strlist | |
987 for (p = lst.begin (); p != lst.end (); p++) | |
988 { | 192 { |
989 os << "<text "; | 193 set_linejoin ("round"); |
990 | 194 set_linecap ("round"); |
991 if (name.compare (p->get_family ())) | |
992 os << "font-family=\"" << p->get_family () << "\" "; | |
993 | |
994 if (weight.compare (p->get_weight ())) | |
995 os << "font-weight=\"" << p->get_weight () << "\" "; | |
996 | |
997 if (angle.compare (p->get_angle ())) | |
998 os << "font-style=\"" << p->get_angle () << "\" "; | |
999 | |
1000 if (size != p->get_size ()) | |
1001 os << "font-size=\"" << p->get_size () << "\" "; | |
1002 | |
1003 os << "y=\"" << - p->get_y () << "\" "; | |
1004 | |
1005 Matrix col = p->get_color (); | |
1006 os << "fill=\"rgb(" << col(0)*255 << "," | |
1007 << col(1)*255 << "," << col(2)*255 << ")\" "; | |
1008 | |
1009 // provide an x coordinate for each character in the string | |
1010 os << "x=\""; | |
1011 std::vector<double> xdata = p->get_xdata (); | |
1012 for (auto q = xdata.begin (); q != xdata.end (); q++) | |
1013 os << (*q) << " "; | |
1014 os << '"'; | |
1015 | |
1016 os << '>'; | |
1017 | |
1018 // translate unicode and special xml characters | |
1019 if (p->get_code ()) | |
1020 os << "&#" << p->get_code () << ";"; | |
1021 else | |
1022 { | |
1023 const std::string str = p->get_string (); | |
1024 for (auto q = str.begin (); q != str.end (); q++) | |
1025 { | |
1026 std::stringstream chr; | |
1027 chr << *q; | |
1028 if (chr.str () == "\"") | |
1029 os << """; | |
1030 else if (chr.str () == "'") | |
1031 os << "'"; | |
1032 else if (chr.str () == "&") | |
1033 os << "&"; | |
1034 else if (chr.str () == "<") | |
1035 os << "<"; | |
1036 else if (chr.str () == ">") | |
1037 os << ">"; | |
1038 else | |
1039 os << chr.str (); | |
1040 } | |
1041 } | |
1042 os << "</text>"; | |
1043 } | |
1044 os << "</g>"; | |
1045 | |
1046 return os.str (); | |
1047 } | |
1048 | |
1049 std::string | |
1050 gl2ps_renderer::strlist_to_ps (double x, double y, double z, | |
1051 Matrix box, double rotation, | |
1052 std::list<text_renderer::string>& lst) | |
1053 { | |
1054 if (lst.empty ()) | |
1055 return ""; | |
1056 else if (lst.size () == 1) | |
1057 { | |
1058 static bool warned = false; | |
1059 // This may be an svg image, not handled in native eps format. | |
1060 if (! lst.front ().get_svg_element ().empty ()) | |
1061 { | |
1062 if (! warned) | |
1063 { | |
1064 warned = true; | |
1065 warning_with_id ("Octave:print:unhandled-svg-content", | |
1066 "print: unhandled LaTeX strings. " | |
1067 "Use -svgconvert option or -d*latex* output " | |
1068 "device."); | |
1069 } | |
1070 return ""; | |
1071 } | |
1072 } | |
1073 | |
1074 // Translate and rotate coordinates in order to use bottom-left alignment | |
1075 fix_strlist_position (x, y, z, box, rotation, lst); | |
1076 Matrix prev_color (1, 3, -1); | |
1077 | |
1078 std::ostringstream ss; | |
1079 ss << "gsave\n"; | |
1080 | |
1081 static bool warned = false; | |
1082 | |
1083 for (const auto& txtobj : lst) | |
1084 { | |
1085 // Color | |
1086 if (txtobj.get_color () != prev_color) | |
1087 { | |
1088 prev_color = txtobj.get_color (); | |
1089 for (int i = 0; i < 3; i++) | |
1090 ss << prev_color(i) << " "; | |
1091 | |
1092 ss << "C\n"; | |
1093 } | |
1094 | |
1095 // String | |
1096 std::string str; | |
1097 if (txtobj.get_code ()) | |
1098 { | |
1099 m_fontname = "Symbol"; | |
1100 str = code_to_symbol (txtobj.get_code ()); | |
1101 } | |
1102 else | |
1103 { | |
1104 m_fontname = select_font (txtobj.get_name (), | |
1105 txtobj.get_weight () == "bold", | |
1106 txtobj.get_angle () == "italic"); | |
1107 | |
1108 // Check that the string is composed of single byte characters | |
1109 const std::string tmpstr = txtobj.get_string (); | |
1110 const uint8_t *c | |
1111 = reinterpret_cast<const uint8_t *> (tmpstr.c_str ()); | |
1112 | |
1113 for (std::size_t i = 0; i < tmpstr.size ();) | |
1114 { | |
1115 int mblen = octave_u8_strmblen_wrapper (c + i); | |
1116 | |
1117 // Replace multibyte or non ascii characters by a question mark | |
1118 if (mblen > 1) | |
1119 { | |
1120 str += "?"; | |
1121 if (! warned) | |
1122 { | |
1123 warning_with_id ("Octave:print:unsupported-multibyte", | |
1124 "print: only ASCII characters are " | |
1125 "supported for EPS and derived " | |
1126 "formats. Use the '-svgconvert' " | |
1127 "option for better font support."); | |
1128 warned = true; | |
1129 } | |
1130 } | |
1131 else if (mblen < 1) | |
1132 { | |
1133 mblen = 1; | |
1134 str += "?"; | |
1135 if (! warned) | |
1136 { | |
1137 warning_with_id ("Octave:print:unhandled-character", | |
1138 "print: only ASCII characters are " | |
1139 "supported for EPS and derived " | |
1140 "formats. Use the '-svgconvert' " | |
1141 "option for better font support."); | |
1142 warned = true; | |
1143 } | |
1144 } | |
1145 else | |
1146 str += tmpstr.at (i); | |
1147 | |
1148 i += mblen; | |
1149 } | |
1150 } | |
1151 | |
1152 escape_character ("\\", str); | |
1153 escape_character ("(", str); | |
1154 escape_character (")", str); | |
1155 | |
1156 ss << "(" << str << ") ["; | |
1157 | |
1158 std::vector<double> xdata = txtobj.get_xdata (); | |
1159 for (std::size_t i = 1; i < xdata.size (); i++) | |
1160 ss << xdata[i] - xdata[i-1] << " "; | |
1161 | |
1162 ss << "10] " << rotation << " " << txtobj.get_x () | |
1163 << " " << txtobj.get_y () << " " << txtobj.get_size () | |
1164 << " /" << m_fontname << " SRX\n"; | |
1165 } | |
1166 | |
1167 ss << "grestore\n"; | |
1168 | |
1169 return ss.str (); | |
1170 } | |
1171 | |
1172 Matrix | |
1173 gl2ps_renderer::render_text (const std::string& txt, | |
1174 double x, double y, double z, | |
1175 int ha, int va, double rotation) | |
1176 { | |
1177 std::string saved_font = m_fontname; | |
1178 | |
1179 if (txt.empty ()) | |
1180 return Matrix (1, 4, 0.0); | |
1181 | |
1182 Matrix bbox; | |
1183 std::string str = txt; | |
1184 std::list<text_renderer::string> lst; | |
1185 | |
1186 text_to_strlist (str, lst, bbox, ha, va, rotation); | |
1187 m_glfcns.glRasterPos3d (x, y, z); | |
1188 | |
1189 // For svg/eps directly dump a preformated text element into gl2ps output | |
1190 if (m_term.find ("svg") != std::string::npos) | |
1191 { | |
1192 std::string elt = strlist_to_svg (x, y, z, bbox, rotation, lst); | |
1193 if (! elt.empty ()) | |
1194 gl2psSpecial (GL2PS_SVG, elt.c_str ()); | |
1195 } | |
1196 else if (m_term.find ("eps") != std::string::npos) | |
1197 { | |
1198 std::string elt = strlist_to_ps (x, y, z, bbox, rotation, lst); | |
1199 if (! elt.empty ()) | |
1200 gl2psSpecial (GL2PS_EPS, elt.c_str ()); | |
1201 | |
1202 } | |
1203 else | |
1204 gl2psTextOpt (str.c_str (), m_fontname.c_str (), m_fontsize, | |
1205 alignment_to_mode (ha, va), rotation); | |
1206 | |
1207 m_fontname = saved_font; | |
1208 | |
1209 return bbox; | |
1210 } | |
1211 | |
1212 void | |
1213 gl2ps_renderer::set_font (const base_properties& props) | |
1214 { | |
1215 opengl_renderer::set_font (props); | |
1216 | |
1217 // Set the interpreter so that text_to_pixels can parse strings properly | |
1218 if (props.has_property ("interpreter")) | |
1219 set_interpreter (props.get ("interpreter").string_value ()); | |
1220 | |
1221 m_fontsize = props.get ("__fontsize_points__").double_value (); | |
1222 | |
1223 caseless_str fn = props.get ("fontname").xtolower ().string_value (); | |
1224 bool isbold | |
1225 =(props.get ("fontweight").xtolower ().string_value () == "bold"); | |
1226 bool isitalic | |
1227 = (props.get ("fontangle").xtolower ().string_value () == "italic"); | |
1228 | |
1229 m_fontname = select_font (fn, isbold, isitalic); | |
1230 } | |
1231 | |
1232 void | |
1233 gl2ps_renderer::draw_image (const image::properties& props) | |
1234 { | |
1235 octave_value cdata = props.get_color_data (); | |
1236 dim_vector dv (cdata.dims ()); | |
1237 int h = dv(0); | |
1238 int w = dv(1); | |
1239 | |
1240 Matrix x = props.get_xdata ().matrix_value (); | |
1241 Matrix y = props.get_ydata ().matrix_value (); | |
1242 | |
1243 // Someone wants us to draw an empty image? No way. | |
1244 if (x.isempty () || y.isempty ()) | |
1245 return; | |
1246 | |
1247 // Sort x/ydata and mark flipped dimensions | |
1248 bool xflip = false; | |
1249 if (x(0) > x(1)) | |
1250 { | |
1251 std::swap (x(0), x(1)); | |
1252 xflip = true; | |
1253 } | |
1254 else if (w > 1 && x(1) == x(0)) | |
1255 x(1) = x(1) + (w-1); | |
1256 | |
1257 bool yflip = false; | |
1258 if (y(0) > y(1)) | |
1259 { | |
1260 std::swap (y(0), y(1)); | |
1261 yflip = true; | |
1262 } | |
1263 else if (h > 1 && y(1) == y(0)) | |
1264 y(1) = y(1) + (h-1); | |
1265 | |
1266 | |
1267 const ColumnVector p0 = m_xform.transform (x(0), y(0), 0); | |
1268 const ColumnVector p1 = m_xform.transform (x(1), y(1), 0); | |
1269 | |
1270 if (math::isnan (p0(0)) || math::isnan (p0(1)) | |
1271 || math::isnan (p1(0)) || math::isnan (p1(1))) | |
1272 { | |
1273 warning ("opengl_renderer: image X,Y data too large to draw"); | |
1274 return; | |
1275 } | |
1276 | |
1277 // image pixel size in screen pixel units | |
1278 float pix_dx, pix_dy; | |
1279 // image pixel size in normalized units | |
1280 float nor_dx, nor_dy; | |
1281 | |
1282 if (w > 1) | |
1283 { | |
1284 pix_dx = (p1(0) - p0(0)) / (w-1); | |
1285 nor_dx = (x(1) - x(0)) / (w-1); | |
1286 } | 195 } |
1287 else | 196 else |
1288 { | 197 { |
1289 const ColumnVector p1w = m_xform.transform (x(1) + 1, y(1), 0); | 198 set_linejoin ("miter"); |
1290 pix_dx = p1w(0) - p0(0); | 199 set_linecap ("square"); |
1291 nor_dx = 1; | |
1292 } | 200 } |
1293 | 201 } |
1294 if (h > 1) | 202 |
203 void set_linestyle (const std::string& s, bool use_stipple = false, | |
204 double linewidth = 0.5) | |
205 { | |
206 opengl_renderer::set_linestyle (s, use_stipple, linewidth); | |
207 | |
208 if (s == "-" && ! use_stipple) | |
209 gl2psDisable (GL2PS_LINE_STIPPLE); | |
210 else | |
211 gl2psEnable (GL2PS_LINE_STIPPLE); | |
212 } | |
213 | |
214 void set_linecap (const std::string& s) | |
215 { | |
216 opengl_renderer::set_linejoin (s); | |
217 | |
218 #if defined (HAVE_GL2PSLINEJOIN) | |
219 if (s == "butt") | |
220 gl2psLineCap (GL2PS_LINE_CAP_BUTT); | |
221 else if (s == "square") | |
222 gl2psLineCap (GL2PS_LINE_CAP_SQUARE); | |
223 else if (s == "round") | |
224 gl2psLineCap (GL2PS_LINE_CAP_ROUND); | |
225 #endif | |
226 } | |
227 | |
228 void set_linejoin (const std::string& s) | |
229 { | |
230 opengl_renderer::set_linejoin (s); | |
231 | |
232 #if defined (HAVE_GL2PSLINEJOIN) | |
233 if (s == "round") | |
234 gl2psLineJoin (GL2PS_LINE_JOIN_ROUND); | |
235 else if (s == "miter") | |
236 gl2psLineJoin (GL2PS_LINE_JOIN_MITER); | |
237 else if (s == "chamfer") | |
238 gl2psLineJoin (GL2PS_LINE_JOIN_BEVEL); | |
239 #endif | |
240 } | |
241 | |
242 void set_polygon_offset (bool on, float offset = 0.0f) | |
243 { | |
244 if (on) | |
1295 { | 245 { |
1296 pix_dy = (p1(1) - p0(1)) / (h-1); | 246 opengl_renderer::set_polygon_offset (on, offset); |
1297 nor_dy = (y(1) - y(0)) / (h-1); | 247 gl2psEnable (GL2PS_POLYGON_OFFSET_FILL); |
1298 } | 248 } |
1299 else | 249 else |
1300 { | 250 { |
1301 const ColumnVector p1h = m_xform.transform (x(1), y(1) + 1, 0); | 251 gl2psDisable (GL2PS_POLYGON_OFFSET_FILL); |
1302 pix_dy = p1h(1) - p0(1); | 252 opengl_renderer::set_polygon_offset (on, offset); |
1303 nor_dy = 1; | |
1304 } | |
1305 | |
1306 // OpenGL won't draw any of the image if its origin is outside the | |
1307 // viewport/clipping plane so we must do the clipping ourselves. | |
1308 | |
1309 int j0, j1, jj, i0, i1, ii; | |
1310 j0 = 0, j1 = w; | |
1311 i0 = 0, i1 = h; | |
1312 | |
1313 float im_xmin = x(0) - nor_dx/2; | |
1314 float im_xmax = x(1) + nor_dx/2; | |
1315 float im_ymin = y(0) - nor_dy/2; | |
1316 float im_ymax = y(1) + nor_dy/2; | |
1317 | |
1318 // Clip to axes or viewport | |
1319 bool do_clip = props.is_clipping (); | |
1320 Matrix vp = get_viewport_scaled (); | |
1321 | |
1322 ColumnVector vp_lim_min | |
1323 = m_xform.untransform (std::numeric_limits <float>::epsilon (), | |
1324 std::numeric_limits <float>::epsilon ()); | |
1325 ColumnVector vp_lim_max = m_xform.untransform (vp(2), vp(3)); | |
1326 | |
1327 if (vp_lim_min(0) > vp_lim_max(0)) | |
1328 std::swap (vp_lim_min(0), vp_lim_max(0)); | |
1329 | |
1330 if (vp_lim_min(1) > vp_lim_max(1)) | |
1331 std::swap (vp_lim_min(1), vp_lim_max(1)); | |
1332 | |
1333 float clip_xmin | |
1334 = do_clip ? (vp_lim_min(0) > m_xmin ? vp_lim_min(0) : m_xmin) | |
1335 : vp_lim_min(0); | |
1336 | |
1337 float clip_ymin | |
1338 = do_clip ? (vp_lim_min(1) > m_ymin ? vp_lim_min(1) : m_ymin) | |
1339 : vp_lim_min(1); | |
1340 | |
1341 float clip_xmax | |
1342 = do_clip ? (vp_lim_max(0) < m_xmax ? vp_lim_max(0) : m_xmax) | |
1343 : vp_lim_max(0); | |
1344 | |
1345 float clip_ymax | |
1346 = do_clip ? (vp_lim_max(1) < m_ymax ? vp_lim_max(1) : m_ymax) | |
1347 : vp_lim_max(1); | |
1348 | |
1349 if (im_xmin < clip_xmin) | |
1350 j0 += (clip_xmin - im_xmin)/nor_dx + 1; | |
1351 | |
1352 if (im_xmax > clip_xmax) | |
1353 j1 -= (im_xmax - clip_xmax)/nor_dx; | |
1354 | |
1355 if (im_ymin < clip_ymin) | |
1356 i0 += (clip_ymin - im_ymin)/nor_dy + 1; | |
1357 | |
1358 if (im_ymax > clip_ymax) | |
1359 i1 -= (im_ymax - clip_ymax)/nor_dy; | |
1360 | |
1361 if (i0 >= i1 || j0 >= j1) | |
1362 return; | |
1363 | |
1364 float zoom_x; | |
1365 m_glfcns.glGetFloatv (GL_ZOOM_X, &zoom_x); | |
1366 float zoom_y; | |
1367 m_glfcns.glGetFloatv (GL_ZOOM_Y, &zoom_y); | |
1368 | |
1369 m_glfcns.glPixelZoom (m_devpixratio * pix_dx, - m_devpixratio * pix_dy); | |
1370 m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0); | |
1371 | |
1372 // Expect RGB data | |
1373 if (dv.ndims () == 3 && dv(2) == 3) | |
1374 { | |
1375 if (cdata.is_double_type ()) | |
1376 { | |
1377 const NDArray xcdata = cdata.array_value (); | |
1378 | |
1379 OCTAVE_LOCAL_BUFFER (GLfloat, a, | |
1380 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1381 | |
1382 for (int i = i0; i < i1; i++) | |
1383 { | |
1384 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1385 { | |
1386 if (! yflip) | |
1387 ii = i; | |
1388 else | |
1389 ii = h - i - 1; | |
1390 | |
1391 if (! xflip) | |
1392 jj = j; | |
1393 else | |
1394 jj = w - j - 1; | |
1395 | |
1396 a[idx] = xcdata(ii, jj, 0); | |
1397 a[idx+1] = xcdata(ii, jj, 1); | |
1398 a[idx+2] = xcdata(ii, jj, 2); | |
1399 } | |
1400 } | |
1401 | |
1402 draw_pixels (j1-j0, i1-i0, a); | |
1403 | |
1404 } | |
1405 else if (cdata.is_single_type ()) | |
1406 { | |
1407 const FloatNDArray xcdata = cdata.float_array_value (); | |
1408 | |
1409 OCTAVE_LOCAL_BUFFER (GLfloat, a, | |
1410 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1411 | |
1412 for (int i = i0; i < i1; i++) | |
1413 { | |
1414 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1415 { | |
1416 if (! yflip) | |
1417 ii = i; | |
1418 else | |
1419 ii = h - i - 1; | |
1420 | |
1421 if (! xflip) | |
1422 jj = j; | |
1423 else | |
1424 jj = w - j - 1; | |
1425 | |
1426 a[idx] = xcdata(ii, jj, 0); | |
1427 a[idx+1] = xcdata(ii, jj, 1); | |
1428 a[idx+2] = xcdata(ii, jj, 2); | |
1429 } | |
1430 } | |
1431 | |
1432 draw_pixels (j1-j0, i1-i0, a); | |
1433 | |
1434 } | |
1435 else if (cdata.is_uint8_type ()) | |
1436 { | |
1437 const uint8NDArray xcdata = cdata.uint8_array_value (); | |
1438 | |
1439 OCTAVE_LOCAL_BUFFER (GLubyte, a, | |
1440 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1441 | |
1442 for (int i = i0; i < i1; i++) | |
1443 { | |
1444 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1445 { | |
1446 if (! yflip) | |
1447 ii = i; | |
1448 else | |
1449 ii = h - i - 1; | |
1450 | |
1451 if (! xflip) | |
1452 jj = j; | |
1453 else | |
1454 jj = w - j - 1; | |
1455 | |
1456 a[idx] = xcdata(ii, jj, 0); | |
1457 a[idx+1] = xcdata(ii, jj, 1); | |
1458 a[idx+2] = xcdata(ii, jj, 2); | |
1459 } | |
1460 } | |
1461 | |
1462 draw_pixels (j1-j0, i1-i0, a); | |
1463 | |
1464 } | |
1465 else if (cdata.is_uint16_type ()) | |
1466 { | |
1467 const uint16NDArray xcdata = cdata.uint16_array_value (); | |
1468 | |
1469 OCTAVE_LOCAL_BUFFER (GLushort, a, | |
1470 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1471 | |
1472 for (int i = i0; i < i1; i++) | |
1473 { | |
1474 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1475 { | |
1476 if (! yflip) | |
1477 ii = i; | |
1478 else | |
1479 ii = h - i - 1; | |
1480 | |
1481 if (! xflip) | |
1482 jj = j; | |
1483 else | |
1484 jj = w - j - 1; | |
1485 | |
1486 a[idx] = xcdata(ii, jj, 0); | |
1487 a[idx+1] = xcdata(ii, jj, 1); | |
1488 a[idx+2] = xcdata(ii, jj, 2); | |
1489 } | |
1490 } | |
1491 | |
1492 draw_pixels (j1-j0, i1-i0, a); | |
1493 | |
1494 } | |
1495 else | |
1496 warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)"); | |
1497 | |
1498 m_glfcns.glPixelZoom (zoom_x, zoom_y); | |
1499 | |
1500 } | 253 } |
1501 } | 254 } |
1502 | 255 |
1503 void | 256 void set_linewidth (float w) |
1504 gl2ps_renderer::draw_pixels (int w, int h, const float *data) | |
1505 { | 257 { |
1506 // Clip data between 0 and 1 for float values | 258 gl2psLineWidth (w); |
1507 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | |
1508 | |
1509 for (int i = 0; i < 3*h*w; i++) | |
1510 tmp_data[i] = (data[i] < 0.0f ? 0.0f : (data[i] > 1.0f ? 1.0f : data[i])); | |
1511 | |
1512 gl2psDrawPixels (w, h, 0, 0, GL_RGB, GL_FLOAT, tmp_data); | |
1513 } | 259 } |
1514 | 260 |
1515 void | 261 private: |
1516 gl2ps_renderer::draw_pixels (int w, int h, const uint8_t *data) | 262 |
1517 { | 263 // Use xform to compute the coordinates of the string list |
1518 // gl2psDrawPixels only supports the GL_FLOAT type. | 264 // that have been parsed by freetype. |
1519 | 265 void fix_strlist_position (double x, double y, double z, |
1520 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | 266 Matrix box, double rotation, |
1521 | 267 std::list<text_renderer::string>& lst); |
1522 static const float maxval = std::numeric_limits<uint8_t>::max (); | 268 |
1523 | 269 // Build an svg text element from a list of parsed strings |
1524 for (int i = 0; i < 3*w*h; i++) | 270 std::string format_svg_element (std::string str, Matrix bbox, |
1525 tmp_data[i] = data[i] / maxval; | 271 double rotation, ColumnVector coord_pix, |
1526 | 272 Matrix color); |
1527 draw_pixels (w, h, tmp_data); | 273 |
1528 } | 274 std::string strlist_to_svg (double x, double y, double z, Matrix box, |
1529 | 275 double rotation, |
1530 void | 276 std::list<text_renderer::string>& lst); |
1531 gl2ps_renderer::draw_pixels (int w, int h, const uint16_t *data) | 277 |
1532 { | 278 // Build a list of postscript commands from a list of parsed strings. |
1533 // gl2psDrawPixels only supports the GL_FLOAT type. | 279 std::string strlist_to_ps (double x, double y, double z, Matrix box, |
1534 | 280 double rotation, |
1535 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | 281 std::list<text_renderer::string>& lst); |
1536 | 282 |
1537 static const float maxval = std::numeric_limits<uint16_t>::max (); | 283 int alignment_to_mode (int ha, int va) const; |
1538 | 284 |
1539 for (int i = 0; i < 3*w*h; i++) | 285 FILE *m_fp; |
1540 tmp_data[i] = data[i] / maxval; | 286 caseless_str m_term; |
1541 | 287 double m_fontsize; |
1542 draw_pixels (w, h, tmp_data); | 288 std::string m_fontname; |
1543 } | 289 bool m_buffer_overflow; |
1544 | 290 std::size_t m_svg_def_index; |
1545 void | 291 }; |
1546 gl2ps_renderer::draw_text (const text::properties& props) | 292 |
1547 { | 293 static bool |
1548 if (props.get_string ().isempty ()) | 294 has_2D_axes (const graphics_handle& h) |
295 { | |
296 bool retval = true; | |
297 | |
298 gh_manager& gh_mgr = __get_gh_manager__ (); | |
299 | |
300 graphics_object go = gh_mgr.get_object (h); | |
301 | |
302 if (! go.valid_object ()) | |
303 return retval; | |
304 | |
305 if (go.isa ("figure") || go.isa ("uipanel")) | |
306 { | |
307 Matrix children = go.get ("children").matrix_value (); | |
308 for (octave_idx_type ii = 0; ii < children.numel (); ii++) | |
309 { | |
310 retval = has_2D_axes (graphics_handle (children(ii))); | |
311 if (! retval) | |
312 break; | |
313 } | |
314 } | |
315 else if (go.isa ("axes")) | |
316 { | |
317 axes::properties& ap | |
318 = reinterpret_cast<axes::properties&> (go.get_properties ()); | |
319 retval = ap.get_is2D (true); | |
320 } | |
321 | |
322 return retval; | |
323 } | |
324 | |
325 static std::string | |
326 get_title (const graphics_handle& h) | |
327 { | |
328 std::string retval; | |
329 | |
330 gh_manager& gh_mgr = __get_gh_manager__ (); | |
331 | |
332 graphics_object go = gh_mgr.get_object (h); | |
333 | |
334 if (! go.valid_object ()) | |
335 return retval; | |
336 | |
337 if (go.isa ("figure")) | |
338 { | |
339 figure::properties& fp | |
340 = reinterpret_cast<figure::properties&> (go.get_properties ()); | |
341 | |
342 retval = fp.get_title (); | |
343 } | |
344 | |
345 return retval; | |
346 } | |
347 | |
348 void | |
349 gl2ps_renderer::draw (const graphics_object& go, const std::string& print_cmd) | |
350 { | |
351 static bool in_draw = false; | |
352 static std::string old_print_cmd; | |
353 static GLint buffsize; | |
354 | |
355 if (! in_draw) | |
356 { | |
357 unwind_protect frame; | |
358 | |
359 frame.protect_var (in_draw); | |
360 | |
361 in_draw = true; | |
362 | |
363 GLint gl2ps_term = GL2PS_PS; | |
364 if (m_term.find ("eps") != std::string::npos) | |
365 gl2ps_term = GL2PS_EPS; | |
366 else if (m_term.find ("pdf") != std::string::npos) | |
367 gl2ps_term = GL2PS_PDF; | |
368 else if (m_term.find ("ps") != std::string::npos) | |
369 gl2ps_term = GL2PS_PS; | |
370 else if (m_term.find ("svg") != std::string::npos) | |
371 gl2ps_term = GL2PS_SVG; | |
372 else if (m_term.find ("pgf") != std::string::npos) | |
373 gl2ps_term = GL2PS_PGF; | |
374 else if (m_term.find ("tex") != std::string::npos) | |
375 gl2ps_term = GL2PS_TEX; | |
376 else | |
377 warning ("gl2ps_renderer::draw: Unknown terminal %s, using 'ps'", | |
378 m_term.c_str ()); | |
379 | |
380 GLint gl2ps_text = 0; | |
381 if (m_term.find ("notxt") != std::string::npos) | |
382 gl2ps_text = GL2PS_NO_TEXT; | |
383 | |
384 // Find Title for plot | |
385 const graphics_handle& myhandle = go.get ("__myhandle__"); | |
386 std::string plot_title = get_title (myhandle); | |
387 if (plot_title.empty ()) | |
388 plot_title = "Octave plot"; | |
389 | |
390 // Default sort order optimizes for 3D plots | |
391 GLint gl2ps_sort = GL2PS_BSP_SORT; | |
392 | |
393 // FIXME: gl2ps does not provide a way to change the sorting algorithm | |
394 // on a viewport basis, we thus disable sorting only if all axes are 2D | |
395 if (has_2D_axes (myhandle)) | |
396 gl2ps_sort = GL2PS_NO_SORT; | |
397 | |
398 // Use a temporary file in case an overflow happens | |
399 std::string tmpfile (sys::tempnam (sys::env::get_temp_directory (), | |
400 "oct-")); | |
401 FILE *tmpf = sys::fopen_tmp (tmpfile, "w+b"); | |
402 | |
403 if (! tmpf) | |
404 error ("gl2ps_renderer::draw: couldn't open temporary file for printing"); | |
405 | |
406 frame.add ([=] () { std::fclose (tmpf); }); | |
407 | |
408 // Reset buffsize, unless this is 2nd pass of a texstandalone print. | |
409 if (m_term.find ("tex") == std::string::npos) | |
410 buffsize = 2*1024*1024; | |
411 else | |
412 buffsize /= 2; | |
413 | |
414 m_buffer_overflow = true; | |
415 | |
416 while (m_buffer_overflow) | |
417 { | |
418 m_buffer_overflow = false; | |
419 buffsize *= 2; | |
420 | |
421 std::fseek (tmpf, 0, SEEK_SET); | |
422 octave_ftruncate_wrapper (fileno (tmpf), 0); | |
423 | |
424 // For LaTeX output the print process uses 2 drawnow() commands. | |
425 // The first one is for the pdf/ps/eps graph to be included. The | |
426 // print_cmd is saved as old_print_cmd. Then the second drawnow() | |
427 // outputs the tex-file and the graphic filename to be included is | |
428 // extracted from old_print_cmd. | |
429 | |
430 std::string include_graph; | |
431 | |
432 std::size_t found_redirect = old_print_cmd.find ('>'); | |
433 | |
434 if (found_redirect != std::string::npos) | |
435 include_graph = old_print_cmd.substr (found_redirect + 1); | |
436 else | |
437 include_graph = old_print_cmd; | |
438 | |
439 std::size_t n_begin = include_graph.find_first_not_of (R"( "')"); | |
440 | |
441 if (n_begin != std::string::npos) | |
442 { | |
443 // Strip any quote characters characters around filename | |
444 std::size_t n_end = include_graph.find_last_not_of (R"( "')"); | |
445 include_graph = include_graph.substr (n_begin, | |
446 n_end - n_begin + 1); | |
447 // Strip path from filename | |
448 n_begin = include_graph.find_last_of (sys::file_ops::dir_sep_chars ()); | |
449 include_graph = include_graph.substr (n_begin + 1); | |
450 } | |
451 else | |
452 include_graph = "foobar-inc"; | |
453 | |
454 // FIXME: workaround gl2ps drawing 2 background planes, the first | |
455 // eventually being black and producing visual artifacts | |
456 const figure::properties& fprop | |
457 = dynamic_cast<const figure::properties&> (go.get_properties ()); | |
458 Matrix c = fprop.get_color_rgb (); | |
459 m_glfcns.glClearColor (c(0), c(1), c(2), 1); | |
460 | |
461 // Allow figures to be printed at arbitrary resolution | |
462 set_device_pixel_ratio (fprop.get___device_pixel_ratio__ ()); | |
463 | |
464 // GL2PS_SILENT was removed to allow gl2ps to print errors on stderr | |
465 GLint ret = gl2psBeginPage (plot_title.c_str (), "Octave", | |
466 nullptr, gl2ps_term, gl2ps_sort, | |
467 (GL2PS_BEST_ROOT | |
468 | gl2ps_text | |
469 | GL2PS_DRAW_BACKGROUND | |
470 | GL2PS_NO_PS3_SHADING | |
471 | GL2PS_USE_CURRENT_VIEWPORT), | |
472 GL_RGBA, 0, nullptr, 0, 0, 0, | |
473 buffsize, tmpf, include_graph.c_str ()); | |
474 if (ret == GL2PS_ERROR) | |
475 { | |
476 old_print_cmd.clear (); | |
477 error ("gl2ps_renderer::draw: gl2psBeginPage returned GL2PS_ERROR"); | |
478 } | |
479 | |
480 opengl_renderer::draw (go); | |
481 | |
482 if (m_buffer_overflow) | |
483 warning ("gl2ps_renderer::draw: retrying with buffer size: %.1E B\n", double (2*buffsize)); | |
484 | |
485 if (! m_buffer_overflow) | |
486 old_print_cmd = print_cmd; | |
487 | |
488 // Don't check return value of gl2psEndPage, it is not meaningful. | |
489 // Errors and warnings are checked after gl2psEndViewport in | |
490 // gl2ps_renderer::draw_axes instead. | |
491 gl2psEndPage (); | |
492 } | |
493 | |
494 // Copy temporary file to pipe | |
495 std::fseek (tmpf, 0, SEEK_SET); | |
496 char str[8192]; // 8 kB is a common kernel buffersize | |
497 std::size_t nread, nwrite; | |
498 nread = 1; | |
499 | |
500 // In EPS terminal read the header line by line and insert a | |
501 // new procedure | |
502 const char *fcn = "/SRX { gsave FCT moveto rotate xshow grestore } BD\n"; | |
503 bool header_found = ! (m_term.find ("eps") != std::string::npos | |
504 || m_term.find ("svg") != std::string::npos); | |
505 | |
506 while (! feof (tmpf) && nread) | |
507 { | |
508 if (! header_found && std::fgets (str, 8192, tmpf)) | |
509 nread = strlen (str); | |
510 else | |
511 nread = std::fread (str, 1, 8192, tmpf); | |
512 | |
513 if (nread) | |
514 { | |
515 if (! header_found && std::strncmp (str, "/SBCR", 5) == 0) | |
516 { | |
517 header_found = true; | |
518 nwrite = std::fwrite (fcn, 1, strlen (fcn), m_fp); | |
519 if (nwrite != strlen (fcn)) | |
520 { | |
521 // FIXME: is this the best thing to do here? | |
522 respond_to_pending_signals (); | |
523 error ("gl2ps_renderer::draw: internal pipe error"); | |
524 } | |
525 } | |
526 else if (m_term.find ("svg") != std::string::npos) | |
527 { | |
528 // FIXME: gl2ps uses pixel units for SVG format. | |
529 // Modify resulting svg to use points instead. | |
530 // Remove this "else if" block, and | |
531 // make header_found true for SVG if gl2ps is fixed. | |
532 | |
533 // Specify number of characters because STR may have | |
534 // come from std::fread and not end with a NUL | |
535 // character. | |
536 std::string srchstr (str, nread); | |
537 std::size_t pos = srchstr.find ("<svg "); | |
538 if (! header_found && pos != std::string::npos) | |
539 { | |
540 header_found = true; | |
541 pos = srchstr.find ("px"); | |
542 if (pos != std::string::npos) | |
543 { | |
544 srchstr[pos+1] = 't'; // "px" -> "pt" | |
545 // Assume the second occurrence is at the same line | |
546 pos = srchstr.find ("px", pos); | |
547 srchstr[pos+1] = 't'; // "px" -> "pt" | |
548 std::strcpy (str, srchstr.c_str ()); | |
549 } | |
550 } | |
551 } | |
552 | |
553 nwrite = std::fwrite (str, 1, nread, m_fp); | |
554 if (nwrite != nread) | |
555 { | |
556 // FIXME: is this the best thing to do here? | |
557 respond_to_pending_signals (); // Clear SIGPIPE signal | |
558 error ("gl2ps_renderer::draw: internal pipe error"); | |
559 } | |
560 } | |
561 } | |
562 } | |
563 else | |
564 opengl_renderer::draw (go); | |
565 } | |
566 | |
567 int | |
568 gl2ps_renderer::alignment_to_mode (int ha, int va) const | |
569 { | |
570 int gl2psa = GL2PS_TEXT_BL; | |
571 | |
572 if (ha == 0) | |
573 { | |
574 if (va == 0 || va == 3) | |
575 gl2psa=GL2PS_TEXT_BL; | |
576 else if (va == 2) | |
577 gl2psa=GL2PS_TEXT_TL; | |
578 else if (va == 1) | |
579 gl2psa=GL2PS_TEXT_CL; | |
580 } | |
581 else if (ha == 2) | |
582 { | |
583 if (va == 0 || va == 3) | |
584 gl2psa=GL2PS_TEXT_BR; | |
585 else if (va == 2) | |
586 gl2psa=GL2PS_TEXT_TR; | |
587 else if (va == 1) | |
588 gl2psa=GL2PS_TEXT_CR; | |
589 } | |
590 else if (ha == 1) | |
591 { | |
592 if (va == 0 || va == 3) | |
593 gl2psa=GL2PS_TEXT_B; | |
594 else if (va == 2) | |
595 gl2psa=GL2PS_TEXT_T; | |
596 else if (va == 1) | |
597 gl2psa=GL2PS_TEXT_C; | |
598 } | |
599 | |
600 return gl2psa; | |
601 } | |
602 | |
603 void | |
604 gl2ps_renderer::fix_strlist_position (double x, double y, double z, | |
605 Matrix box, double rotation, | |
606 std::list<text_renderer::string>& lst) | |
607 { | |
608 for (auto& txtobj : lst) | |
609 { | |
610 // Get pixel coordinates | |
611 ColumnVector coord_pix = get_transform ().transform (x, y, z, false); | |
612 | |
613 // Translate and rotate | |
614 double rot = rotation * 4.0 * atan (1.0) / 180; | |
615 coord_pix(0) += (txtobj.get_x () + box(0))* cos (rot) | |
616 - (txtobj.get_y () + box(1))* sin (rot); | |
617 coord_pix(1) -= (txtobj.get_y () + box(1))* cos (rot) | |
618 + (txtobj.get_x () + box(0))* sin (rot); | |
619 | |
620 GLint vp[4]; | |
621 m_glfcns.glGetIntegerv (GL_VIEWPORT, vp); | |
622 | |
623 txtobj.set_x (coord_pix(0)); | |
624 txtobj.set_y (vp[3] - coord_pix(1)); | |
625 txtobj.set_z (coord_pix(2)); | |
626 } | |
627 } | |
628 | |
629 static std::string | |
630 code_to_symbol (uint32_t code) | |
631 { | |
632 std::string retval; | |
633 | |
634 uint32_t idx = code - 945; | |
635 if (idx < 25) | |
636 { | |
637 std::string characters ("abgdezhqiklmnxoprVstufcyw"); | |
638 retval = characters[idx]; | |
639 return retval; | |
640 } | |
641 | |
642 idx = code - 913; | |
643 if (idx < 25) | |
644 { | |
645 std::string characters ("ABGDEZHQIKLMNXOPRVSTUFCYW"); | |
646 retval = characters[idx]; | |
647 } | |
648 else if (code == 978) | |
649 retval = "U"; | |
650 else if (code == 215) | |
651 retval = "\xb4"; | |
652 else if (code == 177) | |
653 retval = "\xb1"; | |
654 else if (code == 8501) | |
655 retval = "\xc0"; | |
656 else if (code == 8465) | |
657 retval = "\xc1"; | |
658 else if (code == 8242) | |
659 retval = "\xa2"; | |
660 else if (code == 8736) | |
661 retval = "\xd0"; | |
662 else if (code == 172) | |
663 retval = "\xd8"; | |
664 else if (code == 9829) | |
665 retval = "\xa9"; | |
666 else if (code == 8472) | |
667 retval = "\xc3"; | |
668 else if (code == 8706) | |
669 retval = "\xb6"; | |
670 else if (code == 8704) | |
671 retval = "\x22"; | |
672 else if (code == 9827) | |
673 retval = "\xa7"; | |
674 else if (code == 9824) | |
675 retval = "\xaa"; | |
676 else if (code == 8476) | |
677 retval = "\xc2"; | |
678 else if (code == 8734) | |
679 retval = "\xa5"; | |
680 else if (code == 8730) | |
681 retval = "\xd6"; | |
682 else if (code == 8707) | |
683 retval = "\x24"; | |
684 else if (code == 9830) | |
685 retval = "\xa8"; | |
686 else if (code == 8747) | |
687 retval = "\xf2"; | |
688 else if (code == 8727) | |
689 retval = "\x2a"; | |
690 else if (code == 8744) | |
691 retval = "\xda"; | |
692 else if (code == 8855) | |
693 retval = "\xc4"; | |
694 else if (code == 8901) | |
695 retval = "\xd7"; | |
696 else if (code == 8728) | |
697 retval = "\xb0"; | |
698 else if (code == 8745) | |
699 retval = "\xc7"; | |
700 else if (code == 8743) | |
701 retval = "\xd9"; | |
702 else if (code == 8856) | |
703 retval = "\xc6"; | |
704 else if (code == 8729) | |
705 retval = "\xb7"; | |
706 else if (code == 8746) | |
707 retval = "\xc8"; | |
708 else if (code == 8853) | |
709 retval = "\xc5"; | |
710 else if (code == 8804) | |
711 retval = "\xa3"; | |
712 else if (code == 8712) | |
713 retval = "\xce"; | |
714 else if (code == 8839) | |
715 retval = "\xca"; | |
716 else if (code == 8801) | |
717 retval = "\xba"; | |
718 else if (code == 8773) | |
719 retval = "\x40"; | |
720 else if (code == 8834) | |
721 retval = "\xcc"; | |
722 else if (code == 8805) | |
723 retval = "\xb3"; | |
724 else if (code == 8715) | |
725 retval = "\x27"; | |
726 else if (code == 8764) | |
727 retval = "\x7e"; | |
728 else if (code == 8733) | |
729 retval = "\xb5"; | |
730 else if (code == 8838) | |
731 retval = "\xcd"; | |
732 else if (code == 8835) | |
733 retval = "\xc9"; | |
734 else if (code == 8739) | |
735 retval = "\xbd"; | |
736 else if (code == 8776) | |
737 retval = "\xbb"; | |
738 else if (code == 8869) | |
739 retval = "\x5e"; | |
740 else if (code == 8656) | |
741 retval = "\xdc"; | |
742 else if (code == 8592) | |
743 retval = "\xac"; | |
744 else if (code == 8658) | |
745 retval = "\xde"; | |
746 else if (code == 8594) | |
747 retval = "\xae"; | |
748 else if (code == 8596) | |
749 retval = "\xab"; | |
750 else if (code == 8593) | |
751 retval = "\xad"; | |
752 else if (code == 8595) | |
753 retval = "\xaf"; | |
754 else if (code == 8970) | |
755 retval = "\xeb"; | |
756 else if (code == 8971) | |
757 retval = "\xfb"; | |
758 else if (code == 10216) | |
759 retval = "\xe1"; | |
760 else if (code == 10217) | |
761 retval = "\xf1"; | |
762 else if (code == 8968) | |
763 retval = "\xe9"; | |
764 else if (code == 8969) | |
765 retval = "\xf9"; | |
766 else if (code == 8800) | |
767 retval = "\xb9"; | |
768 else if (code == 8230) | |
769 retval = "\xbc"; | |
770 else if (code == 176) | |
771 retval = "\xb0"; | |
772 else if (code == 8709) | |
773 retval = "\xc6"; | |
774 else if (code == 169) | |
775 retval = "\xd3"; | |
776 | |
777 if (retval.empty ()) | |
778 warning ("print: unhandled symbol %d", code); | |
779 | |
780 return retval; | |
781 } | |
782 | |
783 static std::string | |
784 select_font (caseless_str fn, bool isbold, bool isitalic) | |
785 { | |
786 std::transform (fn.begin (), fn.end (), fn.begin (), ::tolower); | |
787 std::string fontname; | |
788 if (fn == "times" || fn == "times-roman") | |
789 { | |
790 if (isitalic && isbold) | |
791 fontname = "Times-BoldItalic"; | |
792 else if (isitalic) | |
793 fontname = "Times-Italic"; | |
794 else if (isbold) | |
795 fontname = "Times-Bold"; | |
796 else | |
797 fontname = "Times-Roman"; | |
798 } | |
799 else if (fn == "courier") | |
800 { | |
801 if (isitalic && isbold) | |
802 fontname = "Courier-BoldOblique"; | |
803 else if (isitalic) | |
804 fontname = "Courier-Oblique"; | |
805 else if (isbold) | |
806 fontname = "Courier-Bold"; | |
807 else | |
808 fontname = "Courier"; | |
809 } | |
810 else if (fn == "symbol") | |
811 fontname = "Symbol"; | |
812 else if (fn == "zapfdingbats") | |
813 fontname = "ZapfDingbats"; | |
814 else | |
815 { | |
816 if (isitalic && isbold) | |
817 fontname = "Helvetica-BoldOblique"; | |
818 else if (isitalic) | |
819 fontname = "Helvetica-Oblique"; | |
820 else if (isbold) | |
821 fontname = "Helvetica-Bold"; | |
822 else | |
823 fontname = "Helvetica"; | |
824 } | |
825 return fontname; | |
826 } | |
827 | |
828 static void | |
829 escape_character (const std::string chr, std::string& str) | |
830 { | |
831 std::size_t idx = str.find (chr); | |
832 while (idx != std::string::npos) | |
833 { | |
834 str.insert (idx, 1, '\\'); | |
835 idx = str.find (chr, idx + 2); | |
836 } | |
837 } | |
838 | |
839 std::string | |
840 gl2ps_renderer::format_svg_element (std::string str, Matrix box, | |
841 double rotation, ColumnVector coord_pix, | |
842 Matrix color) | |
843 { | |
844 // Extract <defs> elements and change their id to avoid conflict with | |
845 // defs coming from another svg string | |
846 std::string::size_type n1 = str.find ("<defs>"); | |
847 if (n1 == std::string::npos) | |
848 return std::string (); | |
849 | |
850 std::string id, new_id; | |
851 n1 = str.find ("<path", ++n1); | |
852 std::string::size_type n2; | |
853 | |
854 while (n1 != std::string::npos) | |
855 { | |
856 // Extract the identifier id='identifier' | |
857 n1 = str.find ("id='", n1) + 4; | |
858 n2 = str.find ("'", n1); | |
859 id = str.substr (n1, n2-n1); | |
860 | |
861 new_id = std::to_string (m_svg_def_index) + "-" + id ; | |
862 | |
863 str.replace (n1, n2-n1, new_id); | |
864 | |
865 std::string::size_type n_ref = str.find ("#" + id); | |
866 | |
867 while (n_ref != std::string::npos) | |
868 { | |
869 str.replace (n_ref + 1, id.length (), new_id); | |
870 n_ref = str.find ("#" + id); | |
871 } | |
872 | |
873 n1 = str.find ("<path", n1); | |
874 } | |
875 | |
876 m_svg_def_index++; | |
877 | |
878 n1 = str.find ("<defs>"); | |
879 n2 = str.find ("</defs>") + 7; | |
880 | |
881 std::string defs = str.substr (n1, n2-n1); | |
882 | |
883 // Extract the group containing the <use> elements and transform its | |
884 // coordinates using the bbox and coordinates info. | |
885 | |
886 // Extract the original viewBox anchor | |
887 n1 = str.find ("viewBox='") + 9; | |
888 if (n1 == std::string::npos) | |
889 return std::string (); | |
890 | |
891 n2 = str.find (" ", n1); | |
892 double original_x0 = std::stod (str.substr (n1, n2-n1)); | |
893 | |
894 n1 = n2+1; | |
895 n2 = str.find (" ", n1); | |
896 double original_y0 = std::stod (str.substr (n1, n2-n1)); | |
897 | |
898 // First look for local transform in the original svg | |
899 std::string orig_trans; | |
900 n1 = str.find ("<g id='page1' transform='"); | |
901 if (n1 != std::string::npos) | |
902 { | |
903 n1 += 25; | |
904 n2 = str.find ("'", n1); | |
905 orig_trans = str.substr (n1, n2-n1); | |
906 n1 = n2 + 1; | |
907 } | |
908 else | |
909 { | |
910 n1 = str.find ("<g id='page1'"); | |
911 n1 += 13; | |
912 } | |
913 | |
914 n2 = str.find ("</g>", n1) + 4; | |
915 | |
916 // The first applied transformation is the right-most | |
917 // 1* Apply original transform | |
918 std::string tform = orig_trans; | |
919 | |
920 // 2* Move the anchor to the final position | |
921 tform = std::string ("translate") | |
922 + "(" + std::to_string (box(0) - original_x0 + coord_pix(0)) | |
923 + "," + std::to_string (-(box(3) + box(1)) - original_y0 + coord_pix(1)) | |
924 + ") " + tform; | |
925 | |
926 // 3* Rotate around the final position | |
927 if (rotation != 0) | |
928 tform = std::string ("rotate") | |
929 + "(" + std::to_string (-rotation) | |
930 + "," + std::to_string (coord_pix(0)) | |
931 + "," + std::to_string (coord_pix(1)) | |
932 + ") " + tform; | |
933 | |
934 // Fill color | |
935 std::string fill = "fill='rgb(" | |
936 + std::to_string (static_cast<uint8_t> (color(0) * 255.0)) + "," | |
937 + std::to_string (static_cast<uint8_t> (color(1) * 255.0)) + "," | |
938 + std::to_string (static_cast<uint8_t> (color(2) * 255.0)) + ")' "; | |
939 | |
940 std::string use_group = "<g " | |
941 + fill | |
942 + "transform='" + tform + "'" | |
943 + str.substr (n1, n2-n1); | |
944 | |
945 return defs + "\n" + use_group; | |
946 } | |
947 | |
948 std::string | |
949 gl2ps_renderer::strlist_to_svg (double x, double y, double z, | |
950 Matrix box, double rotation, | |
951 std::list<text_renderer::string>& lst) | |
952 { | |
953 //Use pixel coordinates to conform to gl2ps | |
954 ColumnVector coord_pix = get_transform ().transform (x, y, z, false); | |
955 | |
956 if (lst.empty ()) | |
957 return ""; | |
958 | |
959 // This may already be an svg image. | |
960 std::string svg = lst.front ().get_svg_element (); | |
961 if (! svg.empty ()) | |
962 return format_svg_element (svg, box, rotation, coord_pix, | |
963 lst.front ().get_color ()); | |
964 | |
965 // Rotation and translation are applied to the whole group | |
966 std::ostringstream os; | |
967 os << R"(<g xml:space="preserve" )"; | |
968 os << "transform=\"" | |
969 << "translate(" << coord_pix(0) + box(0) << "," << coord_pix(1) - box(1) | |
970 << ") rotate(" << -rotation << "," << -box(0) << "," << box(1) | |
971 << ")\" "; | |
972 | |
973 // Use the first entry for the base text font | |
974 auto p = lst.begin (); | |
975 std::string name = p->get_family (); | |
976 std::string weight = p->get_weight (); | |
977 std::string angle = p->get_angle (); | |
978 double size = p->get_size (); | |
979 | |
980 os << "font-family=\"" << name << "\" " | |
981 << "font-weight=\"" << weight << "\" " | |
982 << "font-style=\"" << angle << "\" " | |
983 << "font-size=\"" << size << "\">"; | |
984 | |
985 | |
986 // Build a text element for each element in the strlist | |
987 for (p = lst.begin (); p != lst.end (); p++) | |
988 { | |
989 os << "<text "; | |
990 | |
991 if (name.compare (p->get_family ())) | |
992 os << "font-family=\"" << p->get_family () << "\" "; | |
993 | |
994 if (weight.compare (p->get_weight ())) | |
995 os << "font-weight=\"" << p->get_weight () << "\" "; | |
996 | |
997 if (angle.compare (p->get_angle ())) | |
998 os << "font-style=\"" << p->get_angle () << "\" "; | |
999 | |
1000 if (size != p->get_size ()) | |
1001 os << "font-size=\"" << p->get_size () << "\" "; | |
1002 | |
1003 os << "y=\"" << - p->get_y () << "\" "; | |
1004 | |
1005 Matrix col = p->get_color (); | |
1006 os << "fill=\"rgb(" << col(0)*255 << "," | |
1007 << col(1)*255 << "," << col(2)*255 << ")\" "; | |
1008 | |
1009 // provide an x coordinate for each character in the string | |
1010 os << "x=\""; | |
1011 std::vector<double> xdata = p->get_xdata (); | |
1012 for (auto q = xdata.begin (); q != xdata.end (); q++) | |
1013 os << (*q) << " "; | |
1014 os << '"'; | |
1015 | |
1016 os << '>'; | |
1017 | |
1018 // translate unicode and special xml characters | |
1019 if (p->get_code ()) | |
1020 os << "&#" << p->get_code () << ";"; | |
1021 else | |
1022 { | |
1023 const std::string str = p->get_string (); | |
1024 for (auto q = str.begin (); q != str.end (); q++) | |
1025 { | |
1026 std::stringstream chr; | |
1027 chr << *q; | |
1028 if (chr.str () == "\"") | |
1029 os << """; | |
1030 else if (chr.str () == "'") | |
1031 os << "'"; | |
1032 else if (chr.str () == "&") | |
1033 os << "&"; | |
1034 else if (chr.str () == "<") | |
1035 os << "<"; | |
1036 else if (chr.str () == ">") | |
1037 os << ">"; | |
1038 else | |
1039 os << chr.str (); | |
1040 } | |
1041 } | |
1042 os << "</text>"; | |
1043 } | |
1044 os << "</g>"; | |
1045 | |
1046 return os.str (); | |
1047 } | |
1048 | |
1049 std::string | |
1050 gl2ps_renderer::strlist_to_ps (double x, double y, double z, | |
1051 Matrix box, double rotation, | |
1052 std::list<text_renderer::string>& lst) | |
1053 { | |
1054 if (lst.empty ()) | |
1055 return ""; | |
1056 else if (lst.size () == 1) | |
1057 { | |
1058 static bool warned = false; | |
1059 // This may be an svg image, not handled in native eps format. | |
1060 if (! lst.front ().get_svg_element ().empty ()) | |
1061 { | |
1062 if (! warned) | |
1063 { | |
1064 warned = true; | |
1065 warning_with_id ("Octave:print:unhandled-svg-content", | |
1066 "print: unhandled LaTeX strings. " | |
1067 "Use -svgconvert option or -d*latex* output " | |
1068 "device."); | |
1069 } | |
1070 return ""; | |
1071 } | |
1072 } | |
1073 | |
1074 // Translate and rotate coordinates in order to use bottom-left alignment | |
1075 fix_strlist_position (x, y, z, box, rotation, lst); | |
1076 Matrix prev_color (1, 3, -1); | |
1077 | |
1078 std::ostringstream ss; | |
1079 ss << "gsave\n"; | |
1080 | |
1081 static bool warned = false; | |
1082 | |
1083 for (const auto& txtobj : lst) | |
1084 { | |
1085 // Color | |
1086 if (txtobj.get_color () != prev_color) | |
1087 { | |
1088 prev_color = txtobj.get_color (); | |
1089 for (int i = 0; i < 3; i++) | |
1090 ss << prev_color(i) << " "; | |
1091 | |
1092 ss << "C\n"; | |
1093 } | |
1094 | |
1095 // String | |
1096 std::string str; | |
1097 if (txtobj.get_code ()) | |
1098 { | |
1099 m_fontname = "Symbol"; | |
1100 str = code_to_symbol (txtobj.get_code ()); | |
1101 } | |
1102 else | |
1103 { | |
1104 m_fontname = select_font (txtobj.get_name (), | |
1105 txtobj.get_weight () == "bold", | |
1106 txtobj.get_angle () == "italic"); | |
1107 | |
1108 // Check that the string is composed of single byte characters | |
1109 const std::string tmpstr = txtobj.get_string (); | |
1110 const uint8_t *c | |
1111 = reinterpret_cast<const uint8_t *> (tmpstr.c_str ()); | |
1112 | |
1113 for (std::size_t i = 0; i < tmpstr.size ();) | |
1114 { | |
1115 int mblen = octave_u8_strmblen_wrapper (c + i); | |
1116 | |
1117 // Replace multibyte or non ascii characters by a question mark | |
1118 if (mblen > 1) | |
1119 { | |
1120 str += "?"; | |
1121 if (! warned) | |
1122 { | |
1123 warning_with_id ("Octave:print:unsupported-multibyte", | |
1124 "print: only ASCII characters are " | |
1125 "supported for EPS and derived " | |
1126 "formats. Use the '-svgconvert' " | |
1127 "option for better font support."); | |
1128 warned = true; | |
1129 } | |
1130 } | |
1131 else if (mblen < 1) | |
1132 { | |
1133 mblen = 1; | |
1134 str += "?"; | |
1135 if (! warned) | |
1136 { | |
1137 warning_with_id ("Octave:print:unhandled-character", | |
1138 "print: only ASCII characters are " | |
1139 "supported for EPS and derived " | |
1140 "formats. Use the '-svgconvert' " | |
1141 "option for better font support."); | |
1142 warned = true; | |
1143 } | |
1144 } | |
1145 else | |
1146 str += tmpstr.at (i); | |
1147 | |
1148 i += mblen; | |
1149 } | |
1150 } | |
1151 | |
1152 escape_character ("\\", str); | |
1153 escape_character ("(", str); | |
1154 escape_character (")", str); | |
1155 | |
1156 ss << "(" << str << ") ["; | |
1157 | |
1158 std::vector<double> xdata = txtobj.get_xdata (); | |
1159 for (std::size_t i = 1; i < xdata.size (); i++) | |
1160 ss << xdata[i] - xdata[i-1] << " "; | |
1161 | |
1162 ss << "10] " << rotation << " " << txtobj.get_x () | |
1163 << " " << txtobj.get_y () << " " << txtobj.get_size () | |
1164 << " /" << m_fontname << " SRX\n"; | |
1165 } | |
1166 | |
1167 ss << "grestore\n"; | |
1168 | |
1169 return ss.str (); | |
1170 } | |
1171 | |
1172 Matrix | |
1173 gl2ps_renderer::render_text (const std::string& txt, | |
1174 double x, double y, double z, | |
1175 int ha, int va, double rotation) | |
1176 { | |
1177 std::string saved_font = m_fontname; | |
1178 | |
1179 if (txt.empty ()) | |
1180 return Matrix (1, 4, 0.0); | |
1181 | |
1182 Matrix bbox; | |
1183 std::string str = txt; | |
1184 std::list<text_renderer::string> lst; | |
1185 | |
1186 text_to_strlist (str, lst, bbox, ha, va, rotation); | |
1187 m_glfcns.glRasterPos3d (x, y, z); | |
1188 | |
1189 // For svg/eps directly dump a preformated text element into gl2ps output | |
1190 if (m_term.find ("svg") != std::string::npos) | |
1191 { | |
1192 std::string elt = strlist_to_svg (x, y, z, bbox, rotation, lst); | |
1193 if (! elt.empty ()) | |
1194 gl2psSpecial (GL2PS_SVG, elt.c_str ()); | |
1195 } | |
1196 else if (m_term.find ("eps") != std::string::npos) | |
1197 { | |
1198 std::string elt = strlist_to_ps (x, y, z, bbox, rotation, lst); | |
1199 if (! elt.empty ()) | |
1200 gl2psSpecial (GL2PS_EPS, elt.c_str ()); | |
1201 | |
1202 } | |
1203 else | |
1204 gl2psTextOpt (str.c_str (), m_fontname.c_str (), m_fontsize, | |
1205 alignment_to_mode (ha, va), rotation); | |
1206 | |
1207 m_fontname = saved_font; | |
1208 | |
1209 return bbox; | |
1210 } | |
1211 | |
1212 void | |
1213 gl2ps_renderer::set_font (const base_properties& props) | |
1214 { | |
1215 opengl_renderer::set_font (props); | |
1216 | |
1217 // Set the interpreter so that text_to_pixels can parse strings properly | |
1218 if (props.has_property ("interpreter")) | |
1219 set_interpreter (props.get ("interpreter").string_value ()); | |
1220 | |
1221 m_fontsize = props.get ("__fontsize_points__").double_value (); | |
1222 | |
1223 caseless_str fn = props.get ("fontname").xtolower ().string_value (); | |
1224 bool isbold | |
1225 =(props.get ("fontweight").xtolower ().string_value () == "bold"); | |
1226 bool isitalic | |
1227 = (props.get ("fontangle").xtolower ().string_value () == "italic"); | |
1228 | |
1229 m_fontname = select_font (fn, isbold, isitalic); | |
1230 } | |
1231 | |
1232 void | |
1233 gl2ps_renderer::draw_image (const image::properties& props) | |
1234 { | |
1235 octave_value cdata = props.get_color_data (); | |
1236 dim_vector dv (cdata.dims ()); | |
1237 int h = dv(0); | |
1238 int w = dv(1); | |
1239 | |
1240 Matrix x = props.get_xdata ().matrix_value (); | |
1241 Matrix y = props.get_ydata ().matrix_value (); | |
1242 | |
1243 // Someone wants us to draw an empty image? No way. | |
1244 if (x.isempty () || y.isempty ()) | |
1245 return; | |
1246 | |
1247 // Sort x/ydata and mark flipped dimensions | |
1248 bool xflip = false; | |
1249 if (x(0) > x(1)) | |
1250 { | |
1251 std::swap (x(0), x(1)); | |
1252 xflip = true; | |
1253 } | |
1254 else if (w > 1 && x(1) == x(0)) | |
1255 x(1) = x(1) + (w-1); | |
1256 | |
1257 bool yflip = false; | |
1258 if (y(0) > y(1)) | |
1259 { | |
1260 std::swap (y(0), y(1)); | |
1261 yflip = true; | |
1262 } | |
1263 else if (h > 1 && y(1) == y(0)) | |
1264 y(1) = y(1) + (h-1); | |
1265 | |
1266 | |
1267 const ColumnVector p0 = m_xform.transform (x(0), y(0), 0); | |
1268 const ColumnVector p1 = m_xform.transform (x(1), y(1), 0); | |
1269 | |
1270 if (math::isnan (p0(0)) || math::isnan (p0(1)) | |
1271 || math::isnan (p1(0)) || math::isnan (p1(1))) | |
1272 { | |
1273 warning ("opengl_renderer: image X,Y data too large to draw"); | |
1549 return; | 1274 return; |
1550 | 1275 } |
1551 draw_text_background (props, true); | 1276 |
1552 | 1277 // image pixel size in screen pixel units |
1553 // First set font properties: freetype will use them to compute | 1278 float pix_dx, pix_dy; |
1554 // coordinates and gl2ps will retrieve the color directly from the | 1279 // image pixel size in normalized units |
1555 // feedback buffer | 1280 float nor_dx, nor_dy; |
1556 set_font (props); | 1281 |
1557 set_color (props.get_color_rgb ()); | 1282 if (w > 1) |
1558 | 1283 { |
1559 std::string saved_font = m_fontname; | 1284 pix_dx = (p1(0) - p0(0)) / (w-1); |
1560 | 1285 nor_dx = (x(1) - x(0)) / (w-1); |
1561 // Alignment | 1286 } |
1562 int halign = 0; | 1287 else |
1563 int valign = 0; | 1288 { |
1564 | 1289 const ColumnVector p1w = m_xform.transform (x(1) + 1, y(1), 0); |
1565 if (props.horizontalalignment_is ("center")) | 1290 pix_dx = p1w(0) - p0(0); |
1566 halign = 1; | 1291 nor_dx = 1; |
1567 else if (props.horizontalalignment_is ("right")) | 1292 } |
1568 halign = 2; | 1293 |
1569 | 1294 if (h > 1) |
1570 if (props.verticalalignment_is ("top")) | 1295 { |
1571 valign = 2; | 1296 pix_dy = (p1(1) - p0(1)) / (h-1); |
1572 else if (props.verticalalignment_is ("baseline")) | 1297 nor_dy = (y(1) - y(0)) / (h-1); |
1573 valign = 3; | 1298 } |
1574 else if (props.verticalalignment_is ("middle")) | 1299 else |
1575 valign = 1; | 1300 { |
1576 | 1301 const ColumnVector p1h = m_xform.transform (x(1), y(1) + 1, 0); |
1577 // FIXME: handle margin and surrounding box | 1302 pix_dy = p1h(1) - p0(1); |
1578 // Matrix bbox; | 1303 nor_dy = 1; |
1579 | 1304 } |
1580 const Matrix pos = get_transform ().scale (props.get_data_position ()); | 1305 |
1581 std::string str = props.get_string ().string_vector_value ().join ("\n"); | 1306 // OpenGL won't draw any of the image if its origin is outside the |
1582 | 1307 // viewport/clipping plane so we must do the clipping ourselves. |
1583 render_text (str, pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0, | 1308 |
1584 halign, valign, props.get_rotation ()); | 1309 int j0, j1, jj, i0, i1, ii; |
1585 } | 1310 j0 = 0, j1 = w; |
1311 i0 = 0, i1 = h; | |
1312 | |
1313 float im_xmin = x(0) - nor_dx/2; | |
1314 float im_xmax = x(1) + nor_dx/2; | |
1315 float im_ymin = y(0) - nor_dy/2; | |
1316 float im_ymax = y(1) + nor_dy/2; | |
1317 | |
1318 // Clip to axes or viewport | |
1319 bool do_clip = props.is_clipping (); | |
1320 Matrix vp = get_viewport_scaled (); | |
1321 | |
1322 ColumnVector vp_lim_min | |
1323 = m_xform.untransform (std::numeric_limits <float>::epsilon (), | |
1324 std::numeric_limits <float>::epsilon ()); | |
1325 ColumnVector vp_lim_max = m_xform.untransform (vp(2), vp(3)); | |
1326 | |
1327 if (vp_lim_min(0) > vp_lim_max(0)) | |
1328 std::swap (vp_lim_min(0), vp_lim_max(0)); | |
1329 | |
1330 if (vp_lim_min(1) > vp_lim_max(1)) | |
1331 std::swap (vp_lim_min(1), vp_lim_max(1)); | |
1332 | |
1333 float clip_xmin | |
1334 = do_clip ? (vp_lim_min(0) > m_xmin ? vp_lim_min(0) : m_xmin) | |
1335 : vp_lim_min(0); | |
1336 | |
1337 float clip_ymin | |
1338 = do_clip ? (vp_lim_min(1) > m_ymin ? vp_lim_min(1) : m_ymin) | |
1339 : vp_lim_min(1); | |
1340 | |
1341 float clip_xmax | |
1342 = do_clip ? (vp_lim_max(0) < m_xmax ? vp_lim_max(0) : m_xmax) | |
1343 : vp_lim_max(0); | |
1344 | |
1345 float clip_ymax | |
1346 = do_clip ? (vp_lim_max(1) < m_ymax ? vp_lim_max(1) : m_ymax) | |
1347 : vp_lim_max(1); | |
1348 | |
1349 if (im_xmin < clip_xmin) | |
1350 j0 += (clip_xmin - im_xmin)/nor_dx + 1; | |
1351 | |
1352 if (im_xmax > clip_xmax) | |
1353 j1 -= (im_xmax - clip_xmax)/nor_dx; | |
1354 | |
1355 if (im_ymin < clip_ymin) | |
1356 i0 += (clip_ymin - im_ymin)/nor_dy + 1; | |
1357 | |
1358 if (im_ymax > clip_ymax) | |
1359 i1 -= (im_ymax - clip_ymax)/nor_dy; | |
1360 | |
1361 if (i0 >= i1 || j0 >= j1) | |
1362 return; | |
1363 | |
1364 float zoom_x; | |
1365 m_glfcns.glGetFloatv (GL_ZOOM_X, &zoom_x); | |
1366 float zoom_y; | |
1367 m_glfcns.glGetFloatv (GL_ZOOM_Y, &zoom_y); | |
1368 | |
1369 m_glfcns.glPixelZoom (m_devpixratio * pix_dx, - m_devpixratio * pix_dy); | |
1370 m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0); | |
1371 | |
1372 // Expect RGB data | |
1373 if (dv.ndims () == 3 && dv(2) == 3) | |
1374 { | |
1375 if (cdata.is_double_type ()) | |
1376 { | |
1377 const NDArray xcdata = cdata.array_value (); | |
1378 | |
1379 OCTAVE_LOCAL_BUFFER (GLfloat, a, | |
1380 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1381 | |
1382 for (int i = i0; i < i1; i++) | |
1383 { | |
1384 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1385 { | |
1386 if (! yflip) | |
1387 ii = i; | |
1388 else | |
1389 ii = h - i - 1; | |
1390 | |
1391 if (! xflip) | |
1392 jj = j; | |
1393 else | |
1394 jj = w - j - 1; | |
1395 | |
1396 a[idx] = xcdata(ii, jj, 0); | |
1397 a[idx+1] = xcdata(ii, jj, 1); | |
1398 a[idx+2] = xcdata(ii, jj, 2); | |
1399 } | |
1400 } | |
1401 | |
1402 draw_pixels (j1-j0, i1-i0, a); | |
1403 | |
1404 } | |
1405 else if (cdata.is_single_type ()) | |
1406 { | |
1407 const FloatNDArray xcdata = cdata.float_array_value (); | |
1408 | |
1409 OCTAVE_LOCAL_BUFFER (GLfloat, a, | |
1410 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1411 | |
1412 for (int i = i0; i < i1; i++) | |
1413 { | |
1414 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1415 { | |
1416 if (! yflip) | |
1417 ii = i; | |
1418 else | |
1419 ii = h - i - 1; | |
1420 | |
1421 if (! xflip) | |
1422 jj = j; | |
1423 else | |
1424 jj = w - j - 1; | |
1425 | |
1426 a[idx] = xcdata(ii, jj, 0); | |
1427 a[idx+1] = xcdata(ii, jj, 1); | |
1428 a[idx+2] = xcdata(ii, jj, 2); | |
1429 } | |
1430 } | |
1431 | |
1432 draw_pixels (j1-j0, i1-i0, a); | |
1433 | |
1434 } | |
1435 else if (cdata.is_uint8_type ()) | |
1436 { | |
1437 const uint8NDArray xcdata = cdata.uint8_array_value (); | |
1438 | |
1439 OCTAVE_LOCAL_BUFFER (GLubyte, a, | |
1440 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1441 | |
1442 for (int i = i0; i < i1; i++) | |
1443 { | |
1444 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1445 { | |
1446 if (! yflip) | |
1447 ii = i; | |
1448 else | |
1449 ii = h - i - 1; | |
1450 | |
1451 if (! xflip) | |
1452 jj = j; | |
1453 else | |
1454 jj = w - j - 1; | |
1455 | |
1456 a[idx] = xcdata(ii, jj, 0); | |
1457 a[idx+1] = xcdata(ii, jj, 1); | |
1458 a[idx+2] = xcdata(ii, jj, 2); | |
1459 } | |
1460 } | |
1461 | |
1462 draw_pixels (j1-j0, i1-i0, a); | |
1463 | |
1464 } | |
1465 else if (cdata.is_uint16_type ()) | |
1466 { | |
1467 const uint16NDArray xcdata = cdata.uint16_array_value (); | |
1468 | |
1469 OCTAVE_LOCAL_BUFFER (GLushort, a, | |
1470 static_cast<size_t> (3)*(j1-j0)*(i1-i0)); | |
1471 | |
1472 for (int i = i0; i < i1; i++) | |
1473 { | |
1474 for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3) | |
1475 { | |
1476 if (! yflip) | |
1477 ii = i; | |
1478 else | |
1479 ii = h - i - 1; | |
1480 | |
1481 if (! xflip) | |
1482 jj = j; | |
1483 else | |
1484 jj = w - j - 1; | |
1485 | |
1486 a[idx] = xcdata(ii, jj, 0); | |
1487 a[idx+1] = xcdata(ii, jj, 1); | |
1488 a[idx+2] = xcdata(ii, jj, 2); | |
1489 } | |
1490 } | |
1491 | |
1492 draw_pixels (j1-j0, i1-i0, a); | |
1493 | |
1494 } | |
1495 else | |
1496 warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)"); | |
1497 | |
1498 m_glfcns.glPixelZoom (zoom_x, zoom_y); | |
1499 | |
1500 } | |
1501 } | |
1502 | |
1503 void | |
1504 gl2ps_renderer::draw_pixels (int w, int h, const float *data) | |
1505 { | |
1506 // Clip data between 0 and 1 for float values | |
1507 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | |
1508 | |
1509 for (int i = 0; i < 3*h*w; i++) | |
1510 tmp_data[i] = (data[i] < 0.0f ? 0.0f : (data[i] > 1.0f ? 1.0f : data[i])); | |
1511 | |
1512 gl2psDrawPixels (w, h, 0, 0, GL_RGB, GL_FLOAT, tmp_data); | |
1513 } | |
1514 | |
1515 void | |
1516 gl2ps_renderer::draw_pixels (int w, int h, const uint8_t *data) | |
1517 { | |
1518 // gl2psDrawPixels only supports the GL_FLOAT type. | |
1519 | |
1520 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | |
1521 | |
1522 static const float maxval = std::numeric_limits<uint8_t>::max (); | |
1523 | |
1524 for (int i = 0; i < 3*w*h; i++) | |
1525 tmp_data[i] = data[i] / maxval; | |
1526 | |
1527 draw_pixels (w, h, tmp_data); | |
1528 } | |
1529 | |
1530 void | |
1531 gl2ps_renderer::draw_pixels (int w, int h, const uint16_t *data) | |
1532 { | |
1533 // gl2psDrawPixels only supports the GL_FLOAT type. | |
1534 | |
1535 OCTAVE_LOCAL_BUFFER (float, tmp_data, static_cast<size_t> (3)*w*h); | |
1536 | |
1537 static const float maxval = std::numeric_limits<uint16_t>::max (); | |
1538 | |
1539 for (int i = 0; i < 3*w*h; i++) | |
1540 tmp_data[i] = data[i] / maxval; | |
1541 | |
1542 draw_pixels (w, h, tmp_data); | |
1543 } | |
1544 | |
1545 void | |
1546 gl2ps_renderer::draw_text (const text::properties& props) | |
1547 { | |
1548 if (props.get_string ().isempty ()) | |
1549 return; | |
1550 | |
1551 draw_text_background (props, true); | |
1552 | |
1553 // First set font properties: freetype will use them to compute | |
1554 // coordinates and gl2ps will retrieve the color directly from the | |
1555 // feedback buffer | |
1556 set_font (props); | |
1557 set_color (props.get_color_rgb ()); | |
1558 | |
1559 std::string saved_font = m_fontname; | |
1560 | |
1561 // Alignment | |
1562 int halign = 0; | |
1563 int valign = 0; | |
1564 | |
1565 if (props.horizontalalignment_is ("center")) | |
1566 halign = 1; | |
1567 else if (props.horizontalalignment_is ("right")) | |
1568 halign = 2; | |
1569 | |
1570 if (props.verticalalignment_is ("top")) | |
1571 valign = 2; | |
1572 else if (props.verticalalignment_is ("baseline")) | |
1573 valign = 3; | |
1574 else if (props.verticalalignment_is ("middle")) | |
1575 valign = 1; | |
1576 | |
1577 // FIXME: handle margin and surrounding box | |
1578 // Matrix bbox; | |
1579 | |
1580 const Matrix pos = get_transform ().scale (props.get_data_position ()); | |
1581 std::string str = props.get_string ().string_vector_value ().join ("\n"); | |
1582 | |
1583 render_text (str, pos(0), pos(1), pos.numel () > 2 ? pos(2) : 0.0, | |
1584 halign, valign, props.get_rotation ()); | |
1585 } | |
1586 | 1586 |
1587 OCTAVE_END_NAMESPACE(octave) | 1587 OCTAVE_END_NAMESPACE(octave) |
1588 | 1588 |
1589 #endif | 1589 #endif |
1590 | 1590 |
1591 OCTAVE_BEGIN_NAMESPACE(octave) | 1591 OCTAVE_BEGIN_NAMESPACE(octave) |
1592 | 1592 |
1593 // If the name of the stream begins with '|', open a pipe to the command | 1593 // If the name of the stream begins with '|', open a pipe to the command |
1594 // named by the rest of the string. Otherwise, write to the named file. | 1594 // named by the rest of the string. Otherwise, write to the named file. |
1595 | 1595 |
1596 void | 1596 void |
1597 gl2ps_print (opengl_functions& glfcns, const graphics_object& fig, | 1597 gl2ps_print (opengl_functions& glfcns, const graphics_object& fig, |
1598 const std::string& stream, const std::string& term) | 1598 const std::string& stream, const std::string& term) |
1599 { | 1599 { |
1600 #if defined (HAVE_GL2PS_H) && defined (HAVE_OPENGL) | 1600 #if defined (HAVE_GL2PS_H) && defined (HAVE_OPENGL) |
1601 | 1601 |
1602 // FIXME: should we have a way to create a file that begins with the | 1602 // FIXME: should we have a way to create a file that begins with the |
1603 // character '|'? | 1603 // character '|'? |
1604 | 1604 |
1605 bool have_cmd = stream.length () > 1 && stream[0] == '|'; | 1605 bool have_cmd = stream.length () > 1 && stream[0] == '|'; |
1606 | 1606 |
1607 FILE *m_fp = nullptr; | 1607 FILE *m_fp = nullptr; |
1608 | 1608 |
1609 unwind_protect frame; | 1609 unwind_protect frame; |
1610 | 1610 |
1611 if (have_cmd) | 1611 if (have_cmd) |
1612 { | 1612 { |
1613 // Create process and pipe gl2ps output to it. | 1613 // Create process and pipe gl2ps output to it. |
1614 | 1614 |
1615 std::string cmd = stream.substr (1); | 1615 std::string cmd = stream.substr (1); |
1616 | 1616 |
1617 m_fp = popen (cmd.c_str (), "w"); | 1617 m_fp = popen (cmd.c_str (), "w"); |
1618 | 1618 |
1619 if (! m_fp) | 1619 if (! m_fp) |
1620 error (R"(print: failed to open pipe "%s")", stream.c_str ()); | 1620 error (R"(print: failed to open pipe "%s")", stream.c_str ()); |
1621 | 1621 |
1622 // Need octave:: qualifier here to avoid ambiguity. | 1622 // Need octave:: qualifier here to avoid ambiguity. |
1623 frame.add ([=] () { octave::pclose (m_fp); }); | 1623 frame.add ([=] () { octave::pclose (m_fp); }); |
1624 } | 1624 } |
1625 else | 1625 else |
1626 { | 1626 { |
1627 // Write gl2ps output directly to file. | 1627 // Write gl2ps output directly to file. |
1628 | 1628 |
1629 m_fp = sys::fopen (stream.c_str (), "w"); | 1629 m_fp = sys::fopen (stream.c_str (), "w"); |
1630 | 1630 |
1631 if (! m_fp) | 1631 if (! m_fp) |
1632 error (R"(gl2ps_print: failed to create file "%s")", stream.c_str ()); | 1632 error (R"(gl2ps_print: failed to create file "%s")", stream.c_str ()); |
1633 | 1633 |
1634 frame.add ([=] () { std::fclose (m_fp); }); | 1634 frame.add ([=] () { std::fclose (m_fp); }); |
1635 } | 1635 } |
1636 | 1636 |
1637 gl2ps_renderer rend (glfcns, m_fp, term); | 1637 gl2ps_renderer rend (glfcns, m_fp, term); |
1638 | 1638 |
1639 Matrix pos = fig.get ("position").matrix_value (); | 1639 Matrix pos = fig.get ("position").matrix_value (); |
1640 rend.set_viewport (pos(2), pos(3)); | 1640 rend.set_viewport (pos(2), pos(3)); |
1641 rend.draw (fig, stream); | 1641 rend.draw (fig, stream); |
1642 | 1642 |
1643 // Make sure buffered commands are finished!!! | 1643 // Make sure buffered commands are finished!!! |
1644 rend.finish (); | 1644 rend.finish (); |
1645 | 1645 |
1646 #else | 1646 #else |
1647 | 1647 |
1648 octave_unused_parameter (glfcns); | 1648 octave_unused_parameter (glfcns); |
1649 octave_unused_parameter (fig); | 1649 octave_unused_parameter (fig); |
1650 octave_unused_parameter (stream); | 1650 octave_unused_parameter (stream); |
1651 octave_unused_parameter (term); | 1651 octave_unused_parameter (term); |
1652 | 1652 |
1653 err_disabled_feature ("gl2ps_print", "gl2ps"); | 1653 err_disabled_feature ("gl2ps_print", "gl2ps"); |
1654 | 1654 |
1655 #endif | 1655 #endif |
1656 } | 1656 } |
1657 | 1657 |
1658 OCTAVE_END_NAMESPACE(octave) | 1658 OCTAVE_END_NAMESPACE(octave) |