>From d5dc1b4fb358dbf83aa8c69b6e6f6cd565e9f50c Mon Sep 17 00:00:00 2001 From: Knut Petersen Date: Tue, 8 Jun 2021 23:09:02 +0200 Subject: [PATCH] CAIRO: playing and testing version 4 A new backend based on Cairo. The cairo patch now implements a lilypond backend. To test: Select the desired output formats by any combination of the '--pdf', '--ps' and '--svg' command line options and add '-dbackend=cairo'. The '--svg' option must precede the '-dbackend=cairo' option. Features: Faster than all other backends. It is easy to add support for other image formats. Safer than the postscript backend. Files that fail with the svg backend produce valid svg output with the cairo backend. Known problems: Only setting of standard PDF metadata tags is supported ('Author', 'Title', 'Subject', 'Keywords' and 'Creator'). 'Creation Time' and 'PDF Producer' are set automatically. A lot of seldom used pdf features are not supported (e.g. embedding the lilypond source file into the pdf document). There is also no support for cropping, clipping, and all features that depend on ghostscripts extensions of the postscript language (gs-nevern-embed-fonts, music-font- encodings, etc ...) Internal page hyperlinks: If page is a forward reference (a link to a page that has not been generated yet) cairo silently fails to generate correct hyperlink. As this behaviour is not documented I tend to believe that it is a cairo 1.16 bug, but I have to verify that assumption. SVG files produced by the cairo backend are bigger than SVG files produced by the svg backend. ToDo: A lot of things. ;-) Please test and send comments to 'Knut Petersen' --- aclocal.m4 | 12 + config.make.in | 5 +- configure.ac | 1 + lily/cairo.cc | 1023 +++++++++++++++++++++++++++++++++++++++ lily/main.cc | 4 +- scm/framework-cairo.scm | 285 +++++++++++ 6 files changed, 1327 insertions(+), 3 deletions(-) create mode 100644 lily/cairo.cc create mode 100644 scm/framework-cairo.scm diff --git a/aclocal.m4 b/aclocal.m4 index 0897ead7ab..094585a090 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -857,6 +857,18 @@ AC_DEFUN(STEPMAKE_GOBJECT, [ fi ]) +AC_DEFUN(STEPMAKE_CAIRO, [ + PKG_CHECK_MODULES(CAIRO, $1 >= $3, have_cairo=yes, true) + if test "$have_cairo" = yes; then + AC_DEFINE(HAVE_CAIRO) + AC_SUBST(CAIRO_CFLAGS) + AC_SUBST(CAIRO_LIBS) + else + r="cairo-devel" + ver="`$PKG_CONFIG --modversion $1`" + STEPMAKE_ADD_ENTRY($2, ["$r >= $3 (installed: $ver)"]) + fi +]) AC_DEFUN(STEPMAKE_FREETYPE2, [ PKG_CHECK_MODULES(FREETYPE2, $1 >= $3, have_freetype2=yes, true) diff --git a/config.make.in b/config.make.in index a2f40565ce..395833ca07 100644 --- a/config.make.in +++ b/config.make.in @@ -13,10 +13,10 @@ AR = @AR@ BISON = @BISON@ CC = @CC@ CONFIG_CPPFLAGS = $(FLEXLEXER_CPPFLAGS) @CPPFLAGS@ -CONFIG_CXXFLAGS = @CXXFLAGS@ $(GUILE_CFLAGS) $(FREETYPE2_CFLAGS) $(PANGO_FT2_CFLAGS) +CONFIG_CXXFLAGS = @CXXFLAGS@ $(GUILE_CFLAGS) $(FREETYPE2_CFLAGS) $(PANGO_FT2_CFLAGS) @CAIRO_CFLAGS@ CONFIG_DEFINES = @DEFINES@ CONFIG_LDFLAGS = @LDFLAGS@ -CONFIG_LIBS = @LIBS@ @EXTRA_LIBS@ @GLIB_LIBS@ @GUILE_LIBS@ @PANGO_FT2_LIBS@ @FONTCONFIG_LIBS@ @FREETYPE2_LIBS@ +CONFIG_LIBS = @LIBS@ @EXTRA_LIBS@ @GLIB_LIBS@ @GUILE_LIBS@ @PANGO_FT2_LIBS@ @FONTCONFIG_LIBS@ @FREETYPE2_LIBS@ @CAIRO_LIBS@ CROSS = @cross_compiling@ CXX = @CXX@ CXXABI_LIBS = @CXXABI_LIBS@ @@ -31,6 +31,7 @@ FLEX = @FLEX@ FONTCONFIG_LIBS = @FONTCONFIG_LIBS@ FONTFORGE = @FONTFORGE@ FREETYPE2_LIBS = @FREETYPE2_LIBS@ +CAIRO_LIBS = @CAIRO_LIBS@ GLIB_LIBS = @GLIB_LIBS@ @GOBJECT_LIBS@ GS_API = @GS_API@ GS920 = @GS920@ diff --git a/configure.ac b/configure.ac index 5806508371..1452b8eb3f 100644 --- a/configure.ac +++ b/configure.ac @@ -320,6 +320,7 @@ if test "$have_pangoft2_with_otf_feature" != yes ; then fi STEPMAKE_FONTCONFIG(fontconfig, REQUIRED, 2.4.0) STEPMAKE_FREETYPE2(freetype2, REQUIRED, 2.3.9) +STEPMAKE_CAIRO(cairo, REQUIRED, 1.16.0) STEPMAKE_GLIB(glib-2.0, REQUIRED, 2.38) STEPMAKE_GOBJECT(gobject-2.0, REQUIRED, 2.38) diff --git a/lily/cairo.cc b/lily/cairo.cc new file mode 100644 index 0000000000..6069969f27 --- /dev/null +++ b/lily/cairo.cc @@ -0,0 +1,1023 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2021 Knut Petersen + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#include "freetype.hh" +#include "international.hh" +#include "lily-guile.hh" +#include "open-type-font.hh" +#include "program-option.hh" +#include "warn.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define degToRad(ang) ((ang) * M_PI / 180.0) + +using std::string; +using std::set; +using std::map; + +double lily_output_units; +double lily_output_scale; +double lily_paper_height; +double lily_paper_width; +double sf; + +bool gen_pdf = false; +bool gen_ps = false; +bool gen_svg = false; + +string lily_basename; +string cairo_ps_filename; +string cairo_pdf_filename; +string cairo_svg_filename; + +struct lyfontdata { + FT_Face ftf; + cairo_font_face_t *cf; + double scale; +}; + +map fontmap = {}; + +cairo_surface_t *svg_surface; +cairo_t *svg_cr; +int page_num=1; +int page_max; + +setsurs={}; +set::iterator itsurs; + +setcrs={}; +set::iterator itcrs; + +// +// mcr_xxx...() and msur_xxx...() functions loop over the crs and surs sets +// to execute the xxx...(cr, ...) and xxx...(surface, ...) for every cr/surface. +// + +#define MCRA { itcrs=crs.begin(); while (itcrs != crs.end()) { +#define MCRB itcrs++; } } +#define MSURA { itsurs=surs.begin(); while (itsurs != surs.end()) { +#define MSURB itsurs++; } } + +void mcr_cairo_arc(double a, double b, double c, double d, double e) MCRA cairo_arc(*itcrs, a, b, c, d, e); MCRB +void mcr_cairo_close_path(void) MCRA cairo_close_path(*itcrs); MCRB +void mcr_cairo_curve_to(double a, double b, double c, double d, double e, double f) MCRA cairo_curve_to(*itcrs, a, b, c, d, e, f); MCRB +void mcr_cairo_destroy(void) MCRA cairo_destroy(*itcrs); MCRB +void mcr_cairo_fill(void) MCRA cairo_fill(*itcrs); MCRB +void mcr_cairo_get_current_point(double *a, double *b) MCRA cairo_get_current_point(*itcrs, a, b); MCRB +void mcr_cairo_line_to(double a, double b) MCRA cairo_line_to(*itcrs, a, b); MCRB +void mcr_cairo_move_to( double a, double b) MCRA cairo_move_to(*itcrs, a, b); MCRB +void mcr_cairo_new_path(void) MCRA cairo_new_path(*itcrs); MCRB +void mcr_cairo_new_sub_path(void) MCRA cairo_new_sub_path(*itcrs); MCRB +void mcr_cairo_rel_curve_to(double a, double b, double c, double d, double e, double f) MCRA cairo_rel_curve_to(*itcrs, a, b, c, d, e, f); MCRB +void mcr_cairo_rel_line_to(double a, double b) MCRA cairo_rel_line_to(*itcrs, a, b); MCRB +void mcr_cairo_rel_move_to(double a, double b) MCRA cairo_rel_move_to(*itcrs, a, b); MCRB +void mcr_cairo_restore(void) MCRA cairo_restore(*itcrs); MCRB +void mcr_cairo_rotate(double a) MCRA cairo_rotate(*itcrs, a); MCRB +void mcr_cairo_save(void) MCRA cairo_save(*itcrs); MCRB +void mcr_cairo_scale(double a, double b) MCRA cairo_scale(*itcrs, a, b); MCRB +void mcr_cairo_set_dash(const double *a, int b, double c) MCRA cairo_set_dash(*itcrs, a, b, c); MCRB +void mcr_cairo_set_font_face(cairo_font_face_t *a) MCRA cairo_set_font_face(*itcrs, a); MCRB +void mcr_cairo_set_font_size(double a) MCRA cairo_set_font_size(*itcrs, a); MCRB +void mcr_cairo_set_line_cap(cairo_line_cap_t a) MCRA cairo_set_line_cap(*itcrs, a); MCRB +void mcr_cairo_set_line_join(cairo_line_join_t a) MCRA cairo_set_line_join(*itcrs, a); MCRB +void mcr_cairo_set_line_width(double a) MCRA cairo_set_line_width(*itcrs, a); MCRB +void mcr_cairo_set_source_rgba(double a, double b, double c, double d) MCRA cairo_set_source_rgba(*itcrs, a, b, c, d); MCRB +void mcr_cairo_show_glyphs(const cairo_glyph_t *a, int b) MCRA cairo_show_glyphs(*itcrs, a, b); MCRB +void mcr_cairo_show_text(const char *a) MCRA cairo_show_text(*itcrs, a); MCRB +void mcr_cairo_show_page(void) MCRA cairo_show_page(*itcrs); MCRB +void mcr_cairo_stroke(void) MCRA cairo_stroke(*itcrs); MCRB +void mcr_cairo_stroke_preserve(void) MCRA cairo_stroke_preserve(*itcrs); MCRB +void mcr_cairo_tag_begin(const char *a, const char *b) MCRA cairo_tag_begin(*itcrs, a, b); MCRB +void mcr_cairo_tag_end(const char *a) MCRA cairo_tag_end(*itcrs,a); MCRB +void mcr_cairo_translate(double a, double b) MCRA cairo_translate(*itcrs, a, b); MCRB +void msur_cairo_surface_destroy(void) MSURA cairo_surface_destroy(*itsurs); MSURB + + + +LY_DEFINE (ly_cairo_use_font, "ly:cairo-use-font", + 3, 0, 0, (SCM name, SCM file, SCM index), + "CAIRO: Use font name from file with index.") +{ + LY_ASSERT_TYPE (scm_is_string, name, 1); + LY_ASSERT_TYPE (scm_is_string, file, 2); + string fn = ly_scm2string (name); + string fi = ly_scm2string (file); + int ix = 0; + if (scm_is_integer (index)) + ix = from_scm (index); + debug_output (_f ("cairo-use-font: name: %s, file: %s, index: %d", fn.c_str(), fi.c_str(), ix)); + map::iterator itfm = fontmap.find(fn); + if (itfm == fontmap.end()) { + cairo_user_data_key_t key; + FT_Face ft_face = open_ft_face (fi,ix); + if (!FT_HAS_GLYPH_NAMES(ft_face)) + debug_output (_f ("Font '%s' from file '%s' does not have a glyph name table!",fn,fi)); + cairo_font_face_t *font_face = cairo_ft_font_face_create_for_ft_face (ft_face, 0); + cairo_status_t status = cairo_font_face_set_user_data (font_face, &key, ft_face, (cairo_destroy_func_t) FT_Done_Face); + if (status) { + cairo_font_face_destroy (font_face); + FT_Done_Face (ft_face); + error (_f ("Cannot use font '%s' from file '%s'", fn, fi)); + } + fontmap.insert(make_pair(fn, (lyfontdata) {ft_face, font_face, -1.0})); + debug_output (_f(" inserted font %s loaded from %s in fontmap",fn, fi)); + } + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_def_scaled_font, "ly:cairo-def-scaled-font", + 3, 0, 0, (SCM scaledname, SCM fontname, SCM scaling), + "CAIRO: Define scaledname as name for font fontname scaled size scaling." + " Has to be used after ly:cairo-use-font.") +{ + LY_ASSERT_TYPE (scm_is_string, scaledname, 1); + LY_ASSERT_TYPE (scm_is_string, fontname, 2); + LY_ASSERT_TYPE (scm_is_real, scaling, 3); + string sn = ly_scm2string (scaledname); + string fn = ly_scm2string (fontname); + double scalefac = from_scm (scaling); + debug_output (_f ("cairo-def-scaled-font: scaledname: %s, fontname: %s, scaling: %f", sn.c_str(), fn.c_str(), scalefac)); + map::iterator itsn = fontmap.find(sn); + map::iterator itfn = fontmap.find(fn); + // abort if fontname is unknown + if (itfn == fontmap.end()) + error (_f ("ly:cairo-def-scaled-font: execute ly:cairo-use-font before you try to scale a font!")); + else if (itsn == fontmap.end()) + fontmap.insert (make_pair(sn, (lyfontdata) {itfn->second.ftf, itfn->second.cf, scalefac})); + // no else, ignore request if scaledname is already defined, + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_show_named_glyph, "ly:cairo-show-named-glyph", + 2, 0, 0, (SCM scaledname, SCM glyphname), + "CAIRO: show a named glyph of a scaled font.") +{ + LY_ASSERT_TYPE (scm_is_string, scaledname, 1); + LY_ASSERT_TYPE (scm_is_string, glyphname, 2); + string sn = ly_scm2string (scaledname); + string g = ly_scm2string (glyphname); + debug_output (_f ("show_named_glyph: scaledfont: %s glyph: %s",sn.c_str(), g.c_str())); + map::iterator itfm = fontmap.find(sn); + if (itfm == fontmap.end()) + error (_f("ly:cairo-show-named-glyph: font %s has not been opened.",sn.c_str())); + if (itfm->second.scale < 0.0) + error (_f("ly:cairo-show-named-glyph: font %s is not known as a scaled font.",sn.c_str())); + mcr_cairo_set_font_face(itfm->second.cf); + mcr_cairo_set_font_size(itfm->second.scale * lily_output_units); + double cx, cy; + mcr_cairo_get_current_point(&cx, &cy); + cairo_glyph_t oneglyph = {FT_Get_Name_Index(itfm->second.ftf,g.c_str()), cx, cy}; + mcr_cairo_show_glyphs(&oneglyph,1); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_print_glyphs, "ly:cairo-print-glyphs", + 4, 0, 0, (SCM fontname, SCM size, SCM count, SCM glyphs), + "CAIRO: equivalent of our postscript procedure print_glyphs.") +{ + LY_ASSERT_TYPE (scm_is_string, fontname, 1); + LY_ASSERT_TYPE (scm_is_real, size, 2); + LY_ASSERT_TYPE (scm_is_integer, count, 3); + string fn = ly_scm2string (fontname); + double sz = from_scm (size); + double sumw = 0.0; + double startx, starty; + mcr_cairo_get_current_point(&startx, &starty); + int n = from_scm (count); + cairo_glyph_t oneglyph; + debug_output(_f ("print_glyphs font: %s, size: %.12f, glyph count: %d", fn.c_str(), sz, n)); + map::iterator itfm = fontmap.find(fn); + if (itfm != fontmap.end()) + mcr_cairo_set_font_face(itfm->second.cf); + else + error (_f("ly:cairo-print-glyphs detected that font %s has not been opened.",fn.c_str())); + mcr_cairo_set_font_size(sz * lily_output_units); + for(int i=0;i(scm_car(wxyg)); + double x = from_scm(scm_cadr(wxyg)); + double y = from_scm(scm_caddr(wxyg)); + string g = ly_scm2string(scm_cadddr(wxyg)); + if (!FT_HAS_GLYPH_NAMES(itfm->second.ftf)) { + string ga = ("0x" + (g.substr(3,4))); + unsigned int gb = (unsigned int) std::stoul(ga,nullptr,16); + char gc[] = {0,0,0,0,0,0}; + g_unichar_to_utf8((gunichar) gb , gc); + mcr_cairo_show_text(gc); + debug_output (_f ("print_glyphs font: %s, glyph: %s, int: %d u:%s", fn.c_str(), ga.c_str(), gb, gc)); + } else { + oneglyph = { FT_Get_Name_Index(itfm->second.ftf,g.c_str()), startx + (x + sumw)*sf, starty-y*sf }; + mcr_cairo_show_glyphs(&oneglyph,1); + } + sumw = sumw + w; + } + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_path, "ly:cairo-path", + 2, 3, 0, (SCM thickness, SCM exps, SCM cap, SCM join, SCM filled), + "Cairo: draw a path. Accept the moveto, rmoveto lineto, rlineto, curveto, rcurveto and closepath postscript " + "operators with the required number of arguments. ") +{ + LY_ASSERT_TYPE (scm_is_real, thickness, 1); + LY_ASSERT_TYPE (scm_is_pair, exps, 2); + // Set linewidth + double blot = from_scm (thickness); + mcr_cairo_set_line_width(sf * blot); + // Set line-caps-style, default to 'round + if (!SCM_UNBNDP (cap)) { + if (scm_is_eq (cap, ly_symbol2scm ("butt"))) + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_BUTT); + else if (scm_is_eq (cap, ly_symbol2scm ("square"))) + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_SQUARE); + } else + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_ROUND); + // Set line-join-style, default to 'round + if (!SCM_UNBNDP (join)) { + if (scm_is_eq (join, ly_symbol2scm ("miter"))) + mcr_cairo_set_line_join(CAIRO_LINE_JOIN_MITER); + else if (scm_is_eq (join, ly_symbol2scm ("bevel"))) + mcr_cairo_set_line_join(CAIRO_LINE_JOIN_BEVEL); + } else + mcr_cairo_set_line_join(CAIRO_LINE_JOIN_ROUND); + // save to be able to undo cairo_translate + mcr_cairo_save(); + // translate: current point is new cairo origin + double cpx, cpy; + mcr_cairo_get_current_point(&cpx, &cpy); + mcr_cairo_translate(cpx,cpy); + // evaluate drawing primitives given in exps + for (;scm_is_pair (exps); exps = scm_cdr (exps)) { + SCM head = scm_car (exps); + if (scm_is_symbol (head)) { + if (scm_is_eq (head, ly_symbol2scm ("moveto"))) { + debug_output(_f("PATH: moveto")); + mcr_cairo_move_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2)))); + } else if (scm_is_eq (head, ly_symbol2scm ("rmoveto"))){ + debug_output(_f("PATH: rmoveto")); + mcr_cairo_rel_move_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2)))); + } else if (scm_is_eq (head, ly_symbol2scm ("lineto"))){ + debug_output(_f("PATH: lineto")); + mcr_cairo_line_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2)))); + } else if (scm_is_eq (head, ly_symbol2scm ("rlineto"))){ + debug_output(_f("PATH: rlineto")); + mcr_cairo_rel_line_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2)))); + } else if (scm_is_eq (head, ly_symbol2scm ("curveto"))){ + debug_output(_f("PATH: curveto")); + mcr_cairo_curve_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2))), + sf*from_scm (scm_list_ref(exps,to_scm(3))), + -sf*from_scm (scm_list_ref(exps,to_scm(4))), + sf*from_scm (scm_list_ref(exps,to_scm(5))), + -sf*from_scm (scm_list_ref(exps,to_scm(6)))); + } else if (scm_is_eq (head, ly_symbol2scm ("rcurveto"))){ + debug_output(_f("PATH: rcurveto")); + mcr_cairo_rel_curve_to( + sf*from_scm (scm_list_ref(exps,to_scm(1))), + -sf*from_scm (scm_list_ref(exps,to_scm(2))), + sf*from_scm (scm_list_ref(exps,to_scm(3))), + -sf*from_scm (scm_list_ref(exps,to_scm(4))), + sf*from_scm (scm_list_ref(exps,to_scm(5))), + -sf*from_scm (scm_list_ref(exps,to_scm(6)))); + } else if (scm_is_eq (head, ly_symbol2scm ("closepath"))){ + debug_output(_f("PATH: closepath")); + mcr_cairo_close_path(); + } else { + error(_f("PATH: Unexpected postscript operator! ")); + } + } + } + // stroke / fill according to user wishes + bool fill = false; + if (!SCM_UNBNDP (filled)) { + LY_ASSERT_TYPE (scm_is_bool, filled, 5); + fill = from_scm (filled); + } + if (!fill) + mcr_cairo_stroke(); + else if (blot > 0.0) { + mcr_cairo_stroke_preserve(); + mcr_cairo_fill(); + } + else + mcr_cairo_fill(); + // undo cr->translate + mcr_cairo_restore(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_showpage, "ly:cairo-showpage", + 0, 0, 0, (), + "CAIRO: equivalent of the postscript primitive showpage.") +{ + debug_output (_f ("showpage")); + mcr_cairo_show_page(); + if (gen_svg && page_max > page_num) { + crs.erase(svg_cr); + surs.erase(svg_surface); + cairo_surface_destroy(svg_surface); + cairo_destroy(svg_cr); + page_num++; + cairo_svg_filename = lily_basename + "-" + std::to_string(page_num) + ".cairo.svg"; + svg_surface = cairo_svg_surface_create(cairo_svg_filename.c_str(), lily_paper_width, lily_paper_height); + svg_cr=cairo_create(svg_surface); + surs.insert(svg_surface); + crs.insert(svg_cr); + } + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_restore, "ly:cairo-restore", + 0, 0, 0, (), + "CAIRO: equivalent of the postscript primitive restore.") +{ + debug_output (_f ("restore")); + mcr_cairo_restore(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_save, "ly:cairo-save", + 0, 0, 0, (), + "CAIRO: equivalent of the postscript primitive save.") +{ + debug_output (_f ("save")); + mcr_cairo_save(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_moveto, "ly:cairo-moveto", + 2, 0, 0, (SCM varx, SCM vary), + "CAIRO: equivalent of the postscript primitive moveto.") +{ + LY_ASSERT_TYPE (scm_is_real, varx, 1); + LY_ASSERT_TYPE (scm_is_real, vary, 2); + double x=from_scm (varx); + double y=from_scm (vary); + debug_output (_f ("moveto x: %f y: %f", x, y)); + mcr_cairo_move_to(x*sf,-y*sf); + return SCM_UNSPECIFIED; +} + + +LY_DEFINE (ly_cairo_set_basename, "ly:cairo-set-basename", + 5, 0, 0, (SCM str, SCM maxpage, SCM dopdf, SCM dops, SCM dosvg), + "CAIRO: set basename and define formats to generate.") +{ + LY_ASSERT_TYPE (scm_is_string, str, 1); + LY_ASSERT_TYPE (scm_is_integer, maxpage, 2); + LY_ASSERT_TYPE (scm_is_bool, dopdf, 3); + LY_ASSERT_TYPE (scm_is_bool, dops, 4); + LY_ASSERT_TYPE (scm_is_bool, dosvg, 5); + gen_pdf = from_scm (dopdf); + gen_ps = from_scm (dops); + gen_svg = from_scm (dosvg); + lily_basename = ly_scm2string (str); + page_max = from_scm (maxpage); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_lily_output_units, "ly:cairo-set-lily-output-units", + 1, 0, 0, (SCM val), + "CAIRO: set lily-output-units.") +{ + LY_ASSERT_TYPE (scm_is_real, val, 1); + lily_output_units = from_scm (val); + sf = lily_output_units * lily_output_scale; + debug_output (_f ("set-lily-output-units: %f, sf: %f",lily_output_units, sf)); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_output_scale, "ly:cairo-set-output-scale", + 1, 0, 0, (SCM val), + "CAIRO: set output-scale.") +{ + LY_ASSERT_TYPE (scm_is_real, val, 1); + lily_output_scale = from_scm (val); + sf = lily_output_units * lily_output_scale; + debug_output (_f ("set-output-scale: %f sf: %f",lily_output_scale, sf)); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_paper_height, "ly:cairo-set-paper-height", + 1, 0, 0, (SCM val), + "CAIRO: set paper-height.") +{ + LY_ASSERT_TYPE (scm_is_real, val, 1); + lily_paper_height = from_scm (val); + debug_output (_f ("set-paper-height: %f",lily_paper_height)); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_paper_width, "ly:cairo-set-paper-width", + 1, 0, 0, (SCM val), + "CAIRO: set paper-width.") +{ + LY_ASSERT_TYPE (scm_is_real, val, 1); + lily_paper_width = from_scm (val); + debug_output (_f ("set-paper-width: %f",lily_paper_width)); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_setrgbacolor, "ly:cairo-setrgbacolor", + 4, 0, 0, (SCM varr, SCM varg, SCM varb, SCM vara), + "CAIRO equivalent of our postscript procedure setrgbacolor" + " defined in music-drawing-routines.ps.") +{ + LY_ASSERT_TYPE (scm_is_real, varr, 1); + LY_ASSERT_TYPE (scm_is_real, varg, 2); + LY_ASSERT_TYPE (scm_is_real, varb, 3); + LY_ASSERT_TYPE (scm_is_real, vara, 4); + double r = from_scm (varr); + double g = from_scm (varg); + double b = from_scm (varb); + double a = from_scm (vara); + mcr_cairo_save(); + mcr_cairo_set_source_rgba(r, g, b, a); + debug_output (_f ("setrgbacolor r:%f g:%f b:%f a:%f", r, g, b, a)); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_resetrgbacolor, "ly:cairo-resetrgbacolor", + 0, 0, 0, (), + "CAIRO equivalent of our postscript procedure resetrgbacolor" + " defined in music-drawing-routines.ps.") +{ + mcr_cairo_restore(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_line, "ly:cairo-draw-line", + 5, 0, 0, (SCM vardx, SCM vardy, SCM varx, SCM vary, SCM blotdiam), + "CAIRO equivalent of our postscript procedure draw_line" + " defined in music-drawing-routines.ps.") +{ + LY_ASSERT_TYPE (scm_is_real, vardx, 1); + LY_ASSERT_TYPE (scm_is_real, vardy, 2); + LY_ASSERT_TYPE (scm_is_real, varx, 3); + LY_ASSERT_TYPE (scm_is_real, vary, 4); + LY_ASSERT_TYPE (scm_is_real, blotdiam, 5); + double dx=from_scm (vardx); + double dy=from_scm (vardy); + double x=from_scm (varx); + double y=from_scm (vary); + double d=from_scm (blotdiam); + mcr_cairo_set_line_width(d*sf); + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_ROUND); + mcr_cairo_rel_move_to(x*sf,-y*sf); + mcr_cairo_rel_line_to(dx*sf,-dy*sf); + mcr_cairo_stroke(); + debug_output (_f ("draw_line dx:%f dy:%f x:%f y:%f blotdiam:%f", + from_scm (vardx), from_scm (vardy), from_scm (varx), + from_scm (vary), from_scm (blotdiam))); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_dashed_line, "ly:cairo-draw-dashed-line", + 6, 0, 0, (SCM vardx, SCM vardy, SCM blotdiam, SCM paton, SCM patoff, SCM phase), + "CAIRO equivalent of our postscript procedure dashed_line" + " defined in music-drawing-routines.ps.") +{ + LY_ASSERT_TYPE (scm_is_real, vardx, 1); + LY_ASSERT_TYPE (scm_is_real, vardy, 2); + LY_ASSERT_TYPE (scm_is_real, blotdiam, 3); + LY_ASSERT_TYPE (scm_is_real, paton, 4); + LY_ASSERT_TYPE (scm_is_real, patoff, 5); + LY_ASSERT_TYPE (scm_is_real, phase, 6); + double dx=from_scm (vardx); + double dy=from_scm (vardy); + double d=from_scm (blotdiam); + double on=from_scm (paton); + double off=from_scm (patoff); + double pha=from_scm (phase); + double pat[] = {on*sf, off*sf}; + mcr_cairo_save(); + mcr_cairo_set_dash(pat,2,pha*sf); + mcr_cairo_set_line_width(d*sf); + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_ROUND); + mcr_cairo_rel_line_to(dx*sf,-dy*sf); + mcr_cairo_stroke(); + mcr_cairo_restore(); + debug_output (_f ("draw-dashed-line dx:%f dy:%f on:%f off:%f phase:%f blotdiam:%f", + dx, dy, on, off, pha, d )); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_round_box, "ly:cairo-draw-round-box", + 5, 0, 0, (SCM left, SCM right, SCM bottom, SCM top, SCM blotdiam), + "CAIRO equivalent of our postscript procedure draw_round_box" + " defined in music-drawing-routines.ps.") +{ + LY_ASSERT_TYPE (scm_is_real, left, 1); + LY_ASSERT_TYPE (scm_is_real, right, 2); + LY_ASSERT_TYPE (scm_is_real, bottom, 3); + LY_ASSERT_TYPE (scm_is_real, top, 4); + LY_ASSERT_TYPE (scm_is_real, blotdiam, 5); + double r = (from_scm (blotdiam) * sf) / 2; + double x = r - (from_scm (left) * sf); + double y = r - (from_scm (bottom) * sf); + double w = (from_scm (right) * sf) - r - x; + double h = (from_scm (top) * sf) - r - y; + debug_output (_f ("draw_round_box width:%f height:%f x:%f y:%f radius:%f", w, h, x, y, r)); + // FIXME correct but inefficient code (pdfs are bigger than necessary) + // possible optimizations: see ps code in music-drawing-routines.ps + if (r == 0) { + mcr_cairo_rel_move_to(x,-y); + mcr_cairo_rel_line_to(0,-h); + mcr_cairo_rel_line_to(w,0); + mcr_cairo_rel_line_to(0,h); + mcr_cairo_rel_line_to(-w,0); + mcr_cairo_close_path(); + mcr_cairo_fill(); + } else { + double cx, cy; + mcr_cairo_rel_move_to(x, -y); + mcr_cairo_get_current_point(&cx, &cy); + mcr_cairo_new_sub_path(); + mcr_cairo_arc(cx+w,cy-h,r,degToRad(-90),degToRad(0)); + mcr_cairo_arc(cx+w,cy ,r,degToRad(0),degToRad(90)); + mcr_cairo_arc(cx ,cy ,r,degToRad(90),degToRad(180)); + mcr_cairo_arc(cx ,cy-h,r,degToRad(180),degToRad(270)); + mcr_cairo_close_path(); + mcr_cairo_fill(); + } + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_polygon, "ly:cairo-draw-polygon", + 4, 0, 0, (SCM count, SCM points, SCM linewidth, SCM filled), + "CAIRO: equivalent of our postscript procedure draw_polygon.") +{ + LY_ASSERT_TYPE (scm_is_integer, count, 1); + LY_ASSERT_TYPE (scm_is_real, linewidth, 3); + LY_ASSERT_TYPE (scm_is_bool, filled, 4); + int n = from_scm (count); + double blot = from_scm (linewidth); + bool fill = from_scm (filled); + debug_output(_f ("draw_polygon: %s, linewidth %f, points: %d", fill?"filled":"outline", blot, n)); + double cx, cy, x, y; + mcr_cairo_get_current_point(&cx, &cy); + mcr_cairo_set_line_width(blot*sf); + mcr_cairo_set_line_cap(CAIRO_LINE_CAP_BUTT); + mcr_cairo_set_line_join(CAIRO_LINE_JOIN_ROUND); + for (int i=0; i (scm_list_ref(points,to_scm(i*2))); + y = from_scm (scm_list_ref(points,to_scm(i*2+1))); + debug_output(_f("point %d: x=%f, y=%f", i+1, x, y)); + if (i==0) + mcr_cairo_move_to(x*sf+cx,-y*sf+cy); + else + mcr_cairo_line_to(x*sf+cx,-y*sf+cy); + } + mcr_cairo_close_path(); + if (fill) { + mcr_cairo_stroke_preserve(); + mcr_cairo_fill(); + } else + mcr_cairo_stroke(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_circle, "ly:cairo-draw-circle", + 3, 0, 0, (SCM radius, SCM thickness, SCM filled), + "CAIRO: equivalent of our postscript procedure draw_circle.") +{ + LY_ASSERT_TYPE (scm_is_real, radius, 1); + LY_ASSERT_TYPE (scm_is_real, thickness, 2); + LY_ASSERT_TYPE (scm_is_bool, filled, 3); + bool fill = from_scm (filled); + double rad = from_scm (radius); + double blot = from_scm (thickness); + double cx, cy; + mcr_cairo_get_current_point(&cx, &cy); + mcr_cairo_set_line_width(blot*sf); + mcr_cairo_new_sub_path(); + mcr_cairo_arc(cx,cy,rad*sf, 0.0, degToRad(360)); + if (fill) { + mcr_cairo_stroke_preserve(); + mcr_cairo_fill(); + } else + mcr_cairo_stroke(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_ellipse, "ly:cairo-draw-ellipse", + 4, 0, 0, (SCM xradius, SCM yradius, SCM thickness, SCM filled), + "CAIRO: equivalent of our postscript procedure draw_ellipse.") +{ + LY_ASSERT_TYPE (scm_is_real, xradius, 1); + LY_ASSERT_TYPE (scm_is_real, yradius, 2); + LY_ASSERT_TYPE (scm_is_real, thickness, 3); + LY_ASSERT_TYPE (scm_is_bool, filled, 4); + double xrad = from_scm (xradius); + double yrad = from_scm (yradius); + double blot = from_scm (thickness); + bool fill = from_scm (filled); + double cx, cy; + mcr_cairo_save(); + mcr_cairo_get_current_point(&cx, &cy); + mcr_cairo_translate(cx,cy); + mcr_cairo_scale(1,yrad/xrad); + mcr_cairo_new_path(); + mcr_cairo_arc(0,0,xrad*sf,0,degToRad(360)); + mcr_cairo_restore(); + mcr_cairo_set_line_width(blot*sf); + if (fill) { + mcr_cairo_stroke_preserve(); + mcr_cairo_fill(); + } else + mcr_cairo_stroke(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_draw_partial_ellipse, "ly:cairo-draw-partial-ellipse", + 7, 0, 0, (SCM xradius, SCM yradius, SCM startangle, SCM endangle, SCM thickness, SCM connected, SCM filled), + "CAIRO: equivalent of our postscript procedure draw_partial_ellipse.") +{ + LY_ASSERT_TYPE (scm_is_real, xradius, 1); + LY_ASSERT_TYPE (scm_is_real, yradius, 2); + LY_ASSERT_TYPE (scm_is_real, startangle, 3); + LY_ASSERT_TYPE (scm_is_real, endangle, 4); + LY_ASSERT_TYPE (scm_is_real, thickness, 5); + LY_ASSERT_TYPE (scm_is_bool, connected, 6); + LY_ASSERT_TYPE (scm_is_bool, filled, 7); + double xrad = from_scm (xradius); + double yrad = from_scm (yradius); + double sang = from_scm (startangle); + double eang = from_scm (endangle); + double blot = from_scm (thickness); + bool conn = from_scm (connected); + bool fill = from_scm (filled); + double cx, cy; + mcr_cairo_save(); + mcr_cairo_get_current_point(&cx, &cy); + mcr_cairo_translate(cx,cy); + mcr_cairo_scale(1,yrad/xrad); + mcr_cairo_new_path(); + mcr_cairo_arc(0,0,xrad*sf,degToRad(-eang),degToRad(-sang)); + if (conn) + mcr_cairo_close_path(); + mcr_cairo_restore(); + mcr_cairo_set_line_width(blot*sf); + if (fill) { + mcr_cairo_stroke_preserve(); + mcr_cairo_fill(); + } else + mcr_cairo_stroke(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_rotation, "ly:cairo-set-rotation", + 3, 0, 0, (SCM angle, SCM varx, SCM vary), + "CAIRO: equivalent of setrotation.") +{ + LY_ASSERT_TYPE (scm_is_real, angle, 1); + LY_ASSERT_TYPE (scm_is_real, varx, 2); + LY_ASSERT_TYPE (scm_is_real, vary, 3); + double ang = from_scm (angle); + double x = from_scm (varx); + double y = from_scm (vary); + debug_output(_f("set_rotation angle:%f, x:%f, y%f", ang, x, y)); + mcr_cairo_save(); + mcr_cairo_translate(x*sf,-y*sf); + mcr_cairo_rotate(degToRad(-ang)); + mcr_cairo_translate(-x*sf,y*sf); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_reset_rotation, "ly:cairo-reset-rotation", + 3, 0, 0, (SCM angle, SCM varx, SCM vary), + "CAIRO: equivalent of resetrotation.") +{ + LY_ASSERT_TYPE (scm_is_real, angle, 1); + LY_ASSERT_TYPE (scm_is_real, varx, 2); + LY_ASSERT_TYPE (scm_is_real, vary, 3); + double ang = from_scm (angle); + double x = from_scm (varx); + double y = from_scm (vary); + debug_output(_f("reset_rotation angle:%f, x:%f, y%f", ang, x, y)); + mcr_cairo_restore(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_url_link, "ly:cairo-url-link", + 3, 0, 0, (SCM target, SCM varx, SCM vary), + "CAIRO: Add an URL link.") +{ + LY_ASSERT_TYPE (scm_is_string, target, 1); + LY_ASSERT_TYPE (scm_is_pair, varx, 2); + LY_ASSERT_TYPE (scm_is_pair, vary, 3); + string url = ly_scm2string (target); + double x = from_scm(scm_car (varx)); + double y = from_scm(scm_car (vary)); + double w = from_scm(scm_cdr (varx)) - x; + double h = y - from_scm(scm_cdr (vary)); + double cx, cy; + mcr_cairo_get_current_point(&cx, &cy); + string attr = "rect=[ " + std::to_string (cx + x*sf) + " " + std::to_string (cy - y*sf) + " " + + std::to_string (w*sf) + " " + std::to_string (h*sf) + " ] uri='" + url + "'"; + mcr_cairo_tag_begin( CAIRO_TAG_LINK, attr.c_str()); + mcr_cairo_tag_end( CAIRO_TAG_LINK); + debug_output(_f("url-link %s",attr.c_str())); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_textedit_link, "ly:cairo-textedit-link", + 8, 0, 0, (SCM llx, SCM lly, SCM urx, SCM ury, SCM uri, SCM line, SCM scol, SCM ecol), + "CAIRO: Add a textedit link.") +{ + LY_ASSERT_TYPE (scm_is_real, llx, 1); + LY_ASSERT_TYPE (scm_is_real, lly, 2); + LY_ASSERT_TYPE (scm_is_real, urx, 3); + LY_ASSERT_TYPE (scm_is_real, ury, 4); + LY_ASSERT_TYPE (scm_is_string, uri, 5); + LY_ASSERT_TYPE (scm_is_integer, line, 6); + LY_ASSERT_TYPE (scm_is_integer, scol, 7); + LY_ASSERT_TYPE (scm_is_integer, ecol, 8); + string file = ly_scm2string (uri); + double x = from_scm(llx); + double y = from_scm(lly); + double w = from_scm(urx) - x; + double h = y - from_scm(ury); + int l = from_scm(line); + int sc = from_scm(scol); + int ec = from_scm(ecol); + string attr = "rect=[ " + std::to_string (x*sf) + " " + std::to_string (- y*sf) + " " + + std::to_string (w*sf) + " " + std::to_string (h*sf) + " ] uri='textedit://" + file + + ":" + std::to_string (l) + ":" + std::to_string (sc) + ":" + std::to_string (ec) + "'"; + mcr_cairo_tag_begin( CAIRO_TAG_LINK, attr.c_str()); + mcr_cairo_tag_end( CAIRO_TAG_LINK); + debug_output(_f("textedit-link %s",attr.c_str())); + return SCM_UNSPECIFIED; +} + + + +// FIXME Links to pages that already have been generated work fine, +// links with forward references do _not_ work in cairo 1.16. +// This is either a documentation flaw or a cairo bug. Maybe a named +// destination can be used? I'll have to investigate that. +// +LY_DEFINE (ly_cairo_page_link, "ly:cairo-page-link", + 3, 0, 0, (SCM target, SCM varx, SCM vary), + "CAIRO: Add a page link.") +{ + if (scm_is_bool(target)) + return SCM_UNSPECIFIED; + LY_ASSERT_TYPE (scm_is_integer, target, 1); + LY_ASSERT_TYPE (scm_is_pair, varx, 2); + LY_ASSERT_TYPE (scm_is_pair, vary, 3); + int page = from_scm(target); + double x = from_scm(scm_car (varx)); + double y = from_scm(scm_car (vary)); + double w = from_scm(scm_cdr (varx)) - x; + double h = y - from_scm(scm_cdr (vary)); + double cx, cy; + mcr_cairo_get_current_point(&cx, &cy); + string attr = "rect=[ " + std::to_string (cx + x*sf) + " " + std::to_string (cy - y*sf) + " " + + std::to_string (w*sf) + " " + std::to_string (h*sf) + " ] page=" + std::to_string (page) + " pos=[0.0 0.0]"; + mcr_cairo_tag_begin( CAIRO_TAG_LINK, attr.c_str()); + mcr_cairo_tag_end( CAIRO_TAG_LINK); + debug_output(_f("page-link %s",attr.c_str())); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_set_scale, "ly:cairo-set-scale", + 2, 0, 0, (SCM varx, SCM vary), + "CAIRO: equivalent of setscale.") +{ + LY_ASSERT_TYPE (scm_is_real, varx, 1); + LY_ASSERT_TYPE (scm_is_real, vary, 2); + double x = from_scm (varx); + double y = from_scm (vary); + debug_output(_f("set_scale: x:%f, y%f", x, y)); + mcr_cairo_save(); + mcr_cairo_scale(x,y); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_reset_scale, "ly:cairo-reset-scale", + 0, 0, 0, (), + "CAIRO: equivalent of resetscale.") +{ + debug_output(_f("reset_scale")); + mcr_cairo_restore(); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_unknown, "ly:cairo-unknown", + 0, 0, 0, (), + "CAIRO: detect stencil command 'unknown' and print a warning.") +{ + warning(_f("stencil command 'unknown'!")); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_no_origin, "ly:cairo-no-origin", + 0, 0, 0, (), + "CAIRO: detect stencil command 'no-origin' and print a warning.") +{ + debug_output(_f("stencil command 'no-origin'!")); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_reset_grob_cause, "ly:cairo-reset-grob-cause", + 0, 0, 0, (), + "CAIRO: detect stencil command 'reset-grob-cause' and print a warning.") +{ + warning(_f("stencil command 'reset-grob-cause'")); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_char, "ly:cairo-char", + 2, 0, 0, (SCM font, SCM i), + "CAIRO: detect stencil command 'char' and print a warning.") + +{ + LY_ASSERT_TYPE (scm_is_string, font, 1); + LY_ASSERT_TYPE (scm_is_integer, i, 2); + string fn = ly_scm2string (font); + warning(_f("stencil command 'char' detected, it tries to use font %s!", fn.c_str())); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_embedded_ps, "ly:cairo-embedded-ps", + 1, 0, 0, (SCM postscript), + "CAIRO: detect stencil command 'embedded-ps' and print a warning.") +{ + LY_ASSERT_TYPE (scm_is_string, postscript, 1); + string ps = ly_scm2string (postscript); + warning(_f("Cairo is unable to handle embedded postscript!\n")); + debug_output(_f("The embedded postscript code is: %s", ps.c_str())); + return SCM_UNSPECIFIED; +} + + + +LY_DEFINE (ly_cairo_metadata, "ly:cairo-metadata", + 2, 0, 0, (SCM field, SCM value), + "Cairo: Pass metadata to pdf surface.") +{ + LY_ASSERT_TYPE (scm_is_string, field, 1); + LY_ASSERT_TYPE (scm_is_string, value, 2); + string key = ly_scm2string (field); + string val = ly_scm2string (value); + itsurs=surs.begin(); + while (itsurs !=surs.end()) { + if (cairo_surface_get_type (*itsurs) == CAIRO_SURFACE_TYPE_PDF) { + if (key == "Author") + cairo_pdf_surface_set_metadata (*itsurs,CAIRO_PDF_METADATA_AUTHOR,val.c_str()); + else if (key == "Creator") + cairo_pdf_surface_set_metadata (*itsurs,CAIRO_PDF_METADATA_CREATOR,val.c_str()); + else if (key == "Keywords") + cairo_pdf_surface_set_metadata (*itsurs,CAIRO_PDF_METADATA_KEYWORDS,val.c_str()); + else if (key == "Subject") + cairo_pdf_surface_set_metadata (*itsurs,CAIRO_PDF_METADATA_SUBJECT,val.c_str()); + else if (key == "Title") + cairo_pdf_surface_set_metadata (*itsurs,CAIRO_PDF_METADATA_TITLE,val.c_str()); + } + itsurs++; + } + return SCM_UNSPECIFIED; +} + +LY_DEFINE (ly_cairo_start, "ly:cairo-start", + 0, 0, 0, (), + "CAIRO: Init cairo processing.") +{ + cairo_surface_t *surface; + if (gen_ps) { + cairo_ps_filename = lily_basename + ".cairo.ps"; + surface = cairo_ps_surface_create(cairo_ps_filename.c_str(), lily_paper_width, lily_paper_height); + surs.insert(surface); + crs.insert(cairo_create(surface)); + message (_f ("Generating %s ...", cairo_ps_filename.c_str())); + } + if (gen_pdf) { + cairo_pdf_filename = lily_basename + ".cairo.pdf"; + surface = cairo_pdf_surface_create(cairo_pdf_filename.c_str(), lily_paper_width, lily_paper_height); + surs.insert(surface); + crs.insert(cairo_create(surface)); + message (_f ("Generating %s ...", cairo_pdf_filename.c_str())); + } + if (gen_svg) { + cairo_svg_filename = lily_basename + ((page_max > 1) ? "-1" : "") + ".cairo.svg"; + svg_surface = cairo_svg_surface_create(cairo_svg_filename.c_str(), lily_paper_width, lily_paper_height); + surs.insert(svg_surface); + svg_cr=cairo_create(svg_surface); + crs.insert(svg_cr); + message (_f ("Generating %s ...", cairo_svg_filename.c_str())); + } + return SCM_UNSPECIFIED; +} + +LY_DEFINE (ly_cairo_at_eof, "ly:cairo-at-eof", + 0, 0, 0, (), + "CAIRO: Command cairo to finalize the pdf and to clean up.") +{ + msur_cairo_surface_destroy(); + mcr_cairo_destroy(); + crs.clear(); + surs.clear(); + page_num=1; + debug_output(_f("CAIRO: Finished Pdf, cairo surface and context destroyed.")); + return SCM_UNSPECIFIED; +} diff --git a/lily/main.cc b/lily/main.cc index 5564222b35..e099c0772f 100644 --- a/lily/main.cc +++ b/lily/main.cc @@ -647,8 +647,10 @@ parse_argv (int argc, char **argv) || string (opt->longname_str0_) == "png" || string (opt->longname_str0_) == "ps") add_output_format (opt->longname_str0_); - else if (string (opt->longname_str0_) == "svg") + else if (string (opt->longname_str0_) == "svg") { init_scheme_variables_global += "(backend . svg)\n"; + add_output_format (opt->longname_str0_); + } else if (string (opt->longname_str0_) == "relocate") warning (_ ("The --relocate option is no longer relevant.")); break; diff --git a/scm/framework-cairo.scm b/scm/framework-cairo.scm new file mode 100644 index 0000000000..47c3913aa3 --- /dev/null +++ b/scm/framework-cairo.scm @@ -0,0 +1,285 @@ +;;;; This file is part of LilyPond, the GNU music typesetter. +;;;; +;;;; Copyright (C) 2021 Knut Petersen +;;;; +;;;; LilyPond is free software: you can redistribute it and/or modify +;;;; it under the terms of the GNU General Public License as published by +;;;; the Free Software Foundation, either version 3 of the License, or +;;;; (at your option) any later version. +;;;; +;;;; LilyPond is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with LilyPond. If not, see . + +(define-module (lily framework-cairo)) + +(use-modules (ice-9 string-fun) + (guile) + (lily page) + (lily paper-system) + (srfi srfi-1) + (ice-9 optargs) + (srfi srfi-13) + (lily clip-region) + (lily)) + +(define framework-cairo-module (current-module)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; stencil commands +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (char font i) + (ly:cairo-char font i)) + +(define (circle radius thick fill) + (ly:cairo-draw-circle radius thick fill)) + +(define (dashed-line thick on off dx dy phase) + (ly:cairo-draw-dashed-line dx dy thick on off phase)) + +(define (draw-line thick x1 y1 x2 y2) + (ly:cairo-draw-line (- x2 x1) (- y2 y1) x1 y1 thick)) + +(define (partial-ellipse x-radius y-radius start-angle end-angle thick connect fill) + (ly:cairo-draw-partial-ellipse x-radius y-radius start-angle end-angle thick connect fill)) + +(define (ellipse x-radius y-radius thick fill) + (ly:cairo-draw-ellipse x-radius y-radius thick fill)) + +(define (embedded-ps string) + (ly:cairo-embedded-ps string)) + +(define (glyph-string pango-font postscript-font-name size cid? w-x-y-named-glyphs) + (define (cgs w h x y g) (list w x y g)) + (ly:cairo-print-glyphs postscript-font-name size (length w-x-y-named-glyphs) + (map (lambda (x) (apply cgs x)) w-x-y-named-glyphs))) + +(define (grob-cause offset grob) + (if (ly:get-option 'point-and-click) + (let* ((cause (ly:grob-property grob 'cause)) + (music-origin (if (ly:stream-event? cause) + (ly:event-property cause 'origin))) + (point-and-click (ly:get-option 'point-and-click))) + (if (and + (ly:input-location? music-origin) + (cond ((boolean? point-and-click) point-and-click) + ((symbol? point-and-click) + (ly:in-event-class? cause point-and-click)) + (else (any (lambda (t) + (ly:in-event-class? cause t)) + point-and-click)))) + (let* ((location (ly:input-file-line-char-column music-origin)) + (raw-file (car location)) + (file (if (is-absolute? raw-file) + raw-file + (string-append (ly-getcwd) "/" raw-file))) + (x-ext (ly:grob-extent grob grob X)) + (y-ext (ly:grob-extent grob grob Y))) + + (if (and (< 0 (interval-length x-ext)) + (< 0 (interval-length y-ext))) + (ly:cairo-textedit-link + (+ (car offset) (car x-ext)) + (+ (cdr offset) (car y-ext)) + (+ (car offset) (cdr x-ext)) + (+ (cdr offset) (cdr y-ext)) + (ly:string-percent-encode (ly:string-substitute "\\" "/" file)) + (cadr location) + (caddr location) + (1+ (cadddr location))))))))) + +(define (reset-grob-cause) + (ly:cairo-reset-grob-cause)) + +(define (named-glyph font glyph) + (ly:cairo-show-named-glyph (scaled-font-name font) glyph)) + +(define (no-origin) + (ly:cairo-no-origin)) + +(define (settranslation x y) + (ly:cairo-moveto x y)) + +(define (polygon points blot-diameter filled?) + (ly:cairo-draw-polygon (/ (length points) 2) points blot-diameter filled?)) + +(define (round-filled-box left right bottom top blotdiam) + (ly:cairo-draw-round-box left right bottom top blotdiam)) + +(define* (setcolor r #:optional (g #f) (b #f) (a #f)) + (let* ((colorlist + (if (string? r) + (css->colorlist r) + (if a (list r g b a) + (list r g b)))) + (colors (length colorlist))) + (ly:cairo-setrgbacolor (list-ref colorlist 0) (list-ref colorlist 1) (list-ref colorlist 2) (if (= colors 3) 1.0 (list-ref colorlist 3))))) + +(define (resetcolor) + (ly:cairo-resetrgbacolor)) + +(define (setrotation ang x y) + (ly:cairo-set-rotation ang x y)) + +(define (resetrotation ang x y) + (ly:cairo-reset-rotation ang x y)) + +(define (unknown) + (ly:cairo-unknown)) + +(define (url-link url x y) + (ly:cairo-url-link url x y)) + +(define (page-link page-no x y) + (ly:cairo-page-link page-no x y)) + +(define* (path thickness exps #:optional (cap 'round) (join 'round) (fill? #f)) + (ly:cairo-path thickness exps cap join fill?)) + +(define (setscale x y) + (ly:cairo-set-scale x y)) + +(define (resetscale) + (ly:cairo-reset-scale)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; stencil dispatch list +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define stencil-dispatch-alist + `((char . ,char) + (circle . ,circle) + (dashed-line . ,dashed-line) + (draw-line . ,draw-line) + (partial-ellipse . ,partial-ellipse) + (ellipse . ,ellipse) + (embedded-ps . ,embedded-ps) + (glyph-string . ,glyph-string) + (grob-cause . ,grob-cause) + (named-glyph . ,named-glyph) + (no-origin . ,no-origin) + (settranslation . ,settranslation) + (polygon . ,polygon) + (round-filled-box . ,round-filled-box) + (setcolor . ,setcolor) + (resetcolor . ,resetcolor) + (setrotation . ,setrotation) + (resetrotation . ,resetrotation) + (reset-grob-cause . ,reset-grob-cause) + (unknown . ,unknown) + (url-link . ,url-link) + (page-link . ,page-link) + (path . ,path) + (setscale . ,setscale) + (resetscale . ,resetscale))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; framework +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (scaled-font-name font) + (let* ((name (ly:font-file-name font)) + (magnify (ly:font-magnification font))) + (string-append + "magfont" + (ly:string-substitute " " "_" + (ly:string-substitute "/" "_" + (ly:string-substitute "%" "_" name))) + "m" (string-encode-integer (inexact->exact (round (* 1000 magnify))))))) + +;; define-fonts from backend-library.scm is used in handle-fonts. It needs +;; two arguments, procedures that return a string. +(define (ps-define-font font font-name scaling) + (ly:cairo-def-scaled-font (scaled-font-name font) font-name scaling) "") + +(define (ps-define-pango-pf pango-pf font-name scaling) "") + +(define (handle-fonts paper) + (map + (lambda (font) + (cond + ((string? (ly:font-file-name font)) ;; emmentaler-* fonts + (ly:cairo-use-font + (ly:font-name font) + (ly:find-file (format #f "~a.otf" (ly:font-file-name font))) + #f)) + ((ly:pango-font? font) ;; other fonts + (map + (lambda (psname-filename-fontindex) + (ly:cairo-use-font + (list-ref psname-filename-fontindex 0) + (list-ref psname-filename-fontindex 1) + (list-ref psname-filename-fontindex 2))) + (ly:pango-font-physical-fonts font))) + (else + (ly:error (_ "Cairo: Unexpected font loading problem in procedure handle-fonts!"))))) + (ly:paper-fonts paper)) + (define-fonts paper ps-define-font ps-define-pango-pf)) + +(define (handle-paper-vars paper) + (let* ((ops (ly:output-def-lookup paper 'output-scale)) + (bp (ly:bp 1)) + (w (/ (* ops (ly:output-def-lookup paper 'paper-width)) bp)) + (h (/ (* (ly:output-def-lookup paper 'paper-height) ops) bp))) + (ly:cairo-set-paper-width w) + (ly:cairo-set-paper-height h) + (ly:cairo-set-lily-output-units (/ bp)) + (ly:cairo-set-output-scale ops))) + +(define (handle-metadata header) + (define (metadata-lookup-output overridevar fallbackvar field) + (let* ((overrideval (ly:modules-lookup (list header) overridevar)) + (fallbackval (ly:modules-lookup (list header) fallbackvar)) + (val (if overrideval overrideval fallbackval))) + (if val + (ly:cairo-metadata field (markup->string val (list header)))))) + (if (module? header) + (begin + (ly:cairo-metadata "Creator" (string-append "LilyPond " (lilypond-version) " with experimental cairo patch")) + (metadata-lookup-output 'pdfauthor 'author "Author") + (metadata-lookup-output 'pdftitle 'title "Title") + (metadata-lookup-output 'pdfsubject 'subject "Subject") + (metadata-lookup-output 'pdfkeywords 'keywords "Keywords")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Entry point +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define-public (output-framework basename book scopes fields) + (let* ((port (open-file "/dev/null" "w")) ;; yes. make-paper-outputter needs a port + (outputter (ly:make-paper-outputter port stencil-dispatch-alist)) + (paper (ly:paper-book-paper book)) + (header (ly:paper-book-header book)) + (page-stencils (map page-stencil (ly:paper-book-pages book))) + (landscape? (eq? (ly:output-def-lookup paper 'landscape) #t)) + (page-number (1- (ly:output-def-lookup paper 'first-page-number))) + (page-count (length page-stencils)) + (pdf? (not (equal? #f (member "pdf" (ly:output-formats))))) + (ps? (not (equal? #f (member "ps" (ly:output-formats))))) + (svg? (not (equal? #f (member "svg" (ly:output-formats)))))) + (ly:cairo-set-basename basename page-count pdf? ps? svg?) + (handle-paper-vars paper) + (ly:cairo-start) + (handle-fonts paper) + (handle-metadata header) + (for-each + (lambda (page) + (set! page-number (1+ page-number)) + (ly:outputter-dump-stencil outputter page) + (ly:cairo-showpage)) + page-stencils) + (ly:cairo-at-eof) + (ly:outputter-close outputter))) -- 2.31.1