diff --git a/.gitignore b/.gitignore index 741cc8c3..7529b916 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ pm_to_blib pd_*.json files*.lst res/pod +*.tex +*.sxd +xtest* +*.aux +*.log +*.out \ No newline at end of file diff --git a/Changes b/Changes index 80a62137..83e34e74 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,28 @@ +5.988 2022-05-17 + + - !ChordPro functionality + - Automatically use real sharps and flats in chord names. + Fallback to the ChordProSymbols font if the font doesn't have the + appropriate symbols. + - Add settings.truesf (default: false) to enable/disable this. + - Allow settings.* to be used in %{} substitutions. + - Add meta chords and numchords (list/number of chords used). + - Add config pdf.spacing.diagramchords. + - Allow meta values for directive selectors. + - Re-enable agnostic chord lookup. + - (Wx)(MacOS) Improve prefences dialog. + - Several ABC fixes/improvements. + - (PDF) Add support for background document. + - Markdown export (EXPERIMENTAL). Thanks to Johannes Rumpf. + - LaTeX export (EXPERIMENTAL). Thanks to Johannes Rumpf. + - !BugFixes + - Fix issue #208. + - (Wx) Fix sharps/flats mixup in PreferencesDialog. + 5.987 2022-02-08 - !ChordPro functionality + - Conditional directives can be negated with a trailing ! - (Wx)(MacOS) Improve prefences dialog. - !BugFixes - Add File::HomeDir to dependencies. diff --git a/MANIFEST b/MANIFEST index 95b85ca1..a9d4fcd5 100644 --- a/MANIFEST +++ b/MANIFEST @@ -17,6 +17,8 @@ lib/App/Music/ChordPro/Output/ChordPro.pm lib/App/Music/ChordPro/Output/Common.pm lib/App/Music/ChordPro/Output/Debug.pm lib/App/Music/ChordPro/Output/HTML.pm +lib/App/Music/ChordPro/Output/MMA.pm +lib/App/Music/ChordPro/Output/Markdown.pm lib/App/Music/ChordPro/Output/PDF.pm lib/App/Music/ChordPro/Output/PDF/Writer.pm lib/App/Music/ChordPro/Output/PDF/KeyboardDiagrams.pm @@ -56,6 +58,7 @@ lib/App/Music/ChordPro/res/config/notes/solfege.json lib/App/Music/ChordPro/res/config/roman.json lib/App/Music/ChordPro/res/config/ukulele-ly.json lib/App/Music/ChordPro/res/config/ukulele.json +lib/App/Music/ChordPro/res/examples/bgdemo.pdf lib/App/Music/ChordPro/res/examples/swinglow.cho lib/App/Music/ChordPro/res/fonts/ChordProSymbols.ttf lib/App/Music/ChordPro/res/icons/chordpro.ico @@ -95,7 +98,6 @@ pp/macos/reloc.pl pp/macos/README.html pp/pp2ppl.pl pp/windows/Makefile -pp/windows/abcm2ps.exe pp/windows/chordpro.pp pp/windows/chordpro.rc pp/windows/chordproinst.bmp @@ -178,8 +180,86 @@ t/40_basic01_html.t t/50_encodings.t t/60_transpose.cho t/60_transpose.t +t/70_a2crd.t +t/71_cho.t +t/72_mma.t +t/73_md.t +t/a2crd/t001.cho +t/a2crd/t001.crd +t/a2crd/t002.cho +t/a2crd/t002.crd +t/a2crd/t003.cho +t/a2crd/t003.crd +t/a2crd/t004.cho +t/a2crd/t004.crd +t/a2crd/t005.cho +t/a2crd/t005.crd +t/a2crd/t101.cho +t/a2crd/t101.crd +t/a2crd/t102.cho +t/a2crd/t102.crd +t/a2crd/t103.cho +t/a2crd/t103.crd +t/a2crd/t104.cho +t/a2crd/t104.crd +t/a2crd/t105.cho +t/a2crd/t105.crd +t/a2crd/t106.cho +t/a2crd/t106.crd +t/a2crd/t107.cho +t/a2crd/t107.crd +t/a2crd/t108.cho +t/a2crd/t108.crd +t/a2crd/t109.cho +t/a2crd/t109.crd +t/a2crd/t110.cho +t/a2crd/t110.crd +t/a2crd/t111.cho +t/a2crd/t111.crd +t/a2crd/t113.cho +t/a2crd/t113.crd +t/a2crd/t114.cho +t/a2crd/t114.crd +t/a2crd/t115.cho +t/a2crd/t115.crd t/basic01.cho t/basic02.cho +t/cho/cho001.cho +t/cho/cho001.ref +t/cho/cho002.cho +t/cho/cho002.ref +t/cho/cho003.cho +t/cho/cho003.ref +t/cho/cho004.cho +t/cho/cho004.ref +t/cho/cho005.cho +t/cho/cho005.ref +t/cho/cho006.cho +t/cho/cho006.ref +t/md/30_cho_1.md +t/md/30_cho_2.md +t/md/30_cho_3.md +t/md/a34.md +t/md/a44.md +t/md/cho001.md +t/md/cho002.md +t/md/cho003.md +t/md/cho004.md +t/md/cho005.md +t/mma/a34.cho +t/mma/a34.mma +t/mma/a44.cho +t/mma/a44.mma +t/mma/a68.cho +t/mma/a68.mma +t/mma/deCoda34.cho +t/mma/deCoda34.mma +t/mma/deCoda44a.cho +t/mma/deCoda44a.mma +t/mma/deCoda44.cho +t/mma/deCoda44.mma +t/mma/deCoda68.cho +t/mma/deCoda68.mma t/ref/20_crd_1.crd t/ref/20_crd_2.crd t/ref/20_crd_3.crd @@ -198,3 +278,21 @@ t/ref/40_html_3.html t/ref/60_crd_1.crd t/ref/60_crd_2.crd t/ref/60_crd_3.crd +lib/App/Music/ChordPro/Output/LaTeX.pm +lib/App/Music/ChordPro/res/config/guitar_latex.json +lib/App/Music/ChordPro/res/config/songbook_latex.json +lib/App/Music/ChordPro/res/templates/comment.tt +lib/App/Music/ChordPro/res/templates/guitar_comment.tt +lib/App/Music/ChordPro/res/templates/guitar_image.tt +lib/App/Music/ChordPro/res/templates/guitar_songbook.tt +lib/App/Music/ChordPro/res/templates/image.tt +lib/App/Music/ChordPro/res/templates/songbook.tt +t/74_latex.t +t/latex/30_cho_1.cho +t/latex/30_cho_1.tex +t/latex/30_cho_2.cho +t/latex/30_cho_2.tex +t/latex/t_comment.tt +t/latex/t_config.json +t/latex/t_image.tt +t/latex/t_songbook.tt diff --git a/MANIFEST.CPAN b/MANIFEST.CPAN index df81cff0..707153ba 100644 --- a/MANIFEST.CPAN +++ b/MANIFEST.CPAN @@ -1,3 +1,4 @@ +pp/windows/abcm2ps.exe CPAN/App/Packager.pm CPAN/Data/Properties.pm CPAN/File/HomeDir.pm diff --git a/Makefile.PL b/Makefile.PL index 09213a75..b6034914 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -30,7 +30,7 @@ WriteMakefile PREREQ_PM => { 'App::Packager' => 1.430, 'PDF::API2' => 2.036, - 'Text::Layout' => 0.022, + 'Text::Layout' => 0.028, 'JSON::PP' => 2.27203, 'String::Interpolate::Named' => 1.01, 'File::LoadLines' => 1.02, @@ -38,6 +38,9 @@ WriteMakefile 'Image::Info' => 1.41, 'List::Util' => 1.33, 'Storable' => 3.08, + # These are only used by the LaTeX backend + # 'Template' => 3.010, + # 'LaTeX::Encode' => 0.092.0, }, CONFIGURE_REQUIRES => { diff --git a/docs/assets/images/ex_chordcolour.png b/docs/assets/images/ex_chordcolour.png index ab59b126..795bd66d 100644 Binary files a/docs/assets/images/ex_chordcolour.png and b/docs/assets/images/ex_chordcolour.png differ diff --git a/docs/assets/images/ex_define2.png b/docs/assets/images/ex_define2.png index 64faf450..d81bc0df 100644 Binary files a/docs/assets/images/ex_define2.png and b/docs/assets/images/ex_define2.png differ diff --git a/docs/assets/images/ex_grid1.png b/docs/assets/images/ex_grid1.png index 99f6a4fe..7256a183 100644 Binary files a/docs/assets/images/ex_grid1.png and b/docs/assets/images/ex_grid1.png differ diff --git a/docs/assets/images/ex_image.png b/docs/assets/images/ex_image.png index 73038a1a..2c12c209 100644 Binary files a/docs/assets/images/ex_image.png and b/docs/assets/images/ex_image.png differ diff --git a/docs/assets/images/ex_kbdiagram.png b/docs/assets/images/ex_kbdiagram.png index 539f4b65..8e9f774e 100644 Binary files a/docs/assets/images/ex_kbdiagram.png and b/docs/assets/images/ex_kbdiagram.png differ diff --git a/docs/assets/images/ex_tabcolour.png b/docs/assets/images/ex_tabcolour.png index aab9020c..f065e30a 100644 Binary files a/docs/assets/images/ex_tabcolour.png and b/docs/assets/images/ex_tabcolour.png differ diff --git a/docs/assets/images/page_labels-small.png b/docs/assets/images/page_labels-small.png index 4d8efde2..ef4d0b8c 100644 Binary files a/docs/assets/images/page_labels-small.png and b/docs/assets/images/page_labels-small.png differ diff --git a/docs/assets/images/page_labels.png b/docs/assets/images/page_labels.png index f416c683..22760923 100644 Binary files a/docs/assets/images/page_labels.png and b/docs/assets/images/page_labels.png differ diff --git a/docs/assets/images/pageformats.png b/docs/assets/images/pageformats.png index 953715a9..0bfc7826 100644 Binary files a/docs/assets/images/pageformats.png and b/docs/assets/images/pageformats.png differ diff --git a/docs/assets/images/prf_cr_cfg_1.png b/docs/assets/images/prf_cr_cfg_1.png index 4ca2a398..3397ac3c 100644 Binary files a/docs/assets/images/prf_cr_cfg_1.png and b/docs/assets/images/prf_cr_cfg_1.png differ diff --git a/docs/assets/images/prf_cr_cfg_2.png b/docs/assets/images/prf_cr_cfg_2.png index f0e0e360..b0884bbd 100644 Binary files a/docs/assets/images/prf_cr_cfg_2.png and b/docs/assets/images/prf_cr_cfg_2.png differ diff --git a/docs/assets/images/style_chordii-small.png b/docs/assets/images/style_chordii-small.png index 26bc8b4d..c6e661d4 100644 Binary files a/docs/assets/images/style_chordii-small.png and b/docs/assets/images/style_chordii-small.png differ diff --git a/docs/assets/images/style_chordii.png b/docs/assets/images/style_chordii.png index 49a89ed0..de0d9dd1 100644 Binary files a/docs/assets/images/style_chordii.png and b/docs/assets/images/style_chordii.png differ diff --git a/docs/assets/images/style_dark-small.png b/docs/assets/images/style_dark-small.png index 7ed4a300..c958c640 100644 Binary files a/docs/assets/images/style_dark-small.png and b/docs/assets/images/style_dark-small.png differ diff --git a/docs/assets/images/style_dark.png b/docs/assets/images/style_dark.png index 76f7d03c..fb6df362 100644 Binary files a/docs/assets/images/style_dark.png and b/docs/assets/images/style_dark.png differ diff --git a/docs/assets/images/style_default-small.png b/docs/assets/images/style_default-small.png index 1296a80a..810f6376 100644 Binary files a/docs/assets/images/style_default-small.png and b/docs/assets/images/style_default-small.png differ diff --git a/docs/assets/images/style_default.png b/docs/assets/images/style_default.png index dfc0aa71..046f261a 100644 Binary files a/docs/assets/images/style_default.png and b/docs/assets/images/style_default.png differ diff --git a/docs/assets/images/style_keyboard-small.png b/docs/assets/images/style_keyboard-small.png index f217f60d..d8a5b9b0 100644 Binary files a/docs/assets/images/style_keyboard-small.png and b/docs/assets/images/style_keyboard-small.png differ diff --git a/docs/assets/images/style_keyboard.png b/docs/assets/images/style_keyboard.png index f8dc6a7c..86a4f58c 100644 Binary files a/docs/assets/images/style_keyboard.png and b/docs/assets/images/style_keyboard.png differ diff --git a/docs/assets/images/style_ukulele-small.png b/docs/assets/images/style_ukulele-small.png index 146f47f9..a54d6abb 100644 Binary files a/docs/assets/images/style_ukulele-small.png and b/docs/assets/images/style_ukulele-small.png differ diff --git a/docs/assets/images/style_ukulele.png b/docs/assets/images/style_ukulele.png index 4b0c9b8a..4f933522 100644 Binary files a/docs/assets/images/style_ukulele.png and b/docs/assets/images/style_ukulele.png differ diff --git a/docs/assets/images/verselabels.png b/docs/assets/images/verselabels.png deleted file mode 100644 index df0c35e2..00000000 Binary files a/docs/assets/images/verselabels.png and /dev/null differ diff --git a/docs/assets/pub/config60.schema b/docs/assets/pub/config60.schema index 60fb763a..0beffcfc 100644 --- a/docs/assets/pub/config60.schema +++ b/docs/assets/pub/config60.schema @@ -33,6 +33,9 @@ "module": { "type": "string", "default": "ABC" + }, + "preprocess": { + "type": "object" } }, "required" : [ "type", "handler", "module" ], @@ -139,6 +142,13 @@ "default": false }, + "choruslabels": { + "description": "", + "title": "If false, chorus labels are used as tags.", + "type": "boolean", + "default": true + }, + "columns": { "description": "Number of columns.", "type": "integer", @@ -231,6 +241,12 @@ "minimum" : -12, "maximum" : 12, "format" : "number" + }, + + "truesf": { + "description": "Substitute Unicode sharp/flats in chord names.", + "type": "boolean", + "default": false } } }, @@ -695,6 +711,41 @@ } }, + "latex": { + "title": "LaTeX backend", + "description": "", + "type": "object", + "additionalProperties": false, + "properties": { + "template_include_path": { + "description": "Include paths for templates.", + "additionalProperties": false, + "type" : "array" + }, + "templates" : { + "description": "Templates for LaTeX generation.", + "additionalProperties": false, + "properties": { + "comment": { + "description": "Helper template to render comments.", + "type": "string", + "default" : "comment.tt" + }, + "image": { + "description": "Helper template to render images.", + "type": "string", + "default" : "image.tt" + }, + "songbook": { + "description": "Master template to render the songbook.", + "type": "string", + "default" : "songbook.tt" + } + } + } + } + }, + "parser": { "title": "Preprocessing", "description": "Preprocessing the input.", @@ -1103,6 +1154,10 @@ "description": "Default properties for all pages.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "allOf": [ { "$ref": "#/definitions/tptspec" }, @@ -1124,6 +1179,10 @@ "description": "Properties for per-song title pages.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "allOf": [ { "$ref": "#/definitions/tptspec" }, @@ -1145,6 +1204,10 @@ "description": "Properties of the very first page.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "description": "Defaults to default.", "$ref": "#/definitions/tptspec" diff --git a/docs/content/ChordPro-Configuration-Format-Strings.md b/docs/content/ChordPro-Configuration-Format-Strings.md index f0411b2f..9cb4ddd1 100644 --- a/docs/content/ChordPro-Configuration-Format-Strings.md +++ b/docs/content/ChordPro-Configuration-Format-Strings.md @@ -61,40 +61,48 @@ as shown above. The ChordPro reference implementation provides additional meta data: - * `songindex`: The index (serial number) of the song in the songbook. - + * `chords`: A comma-separated list of chords used in this song. + + * `instrument`: Short for `instrument.type`. + + * `instrument.description`: Set by instrument configs. + For the default guitar config this is `"Guitar, 6 + strings, standard tuning"`. + + * `instrument.type`: The name of the instrument as set by instrument + configs. Default `"guitar"`. + + * `numchords`: The number of chords used in this song. + * `page`: The starting page number of the song. * `pages`: The number of pages of the current song. - * `pagerange`: The pages of the song, either a single page number or - a range like `3-7`. - _`pagerange` is only available for CSV generation, see - [Configuration for CSV output]({{< relref "chordpro-configuration-csv" >}})._ - + * `songindex`: The index (serial number) of the song in the songbook. + * `today`: The current date in the format defined in the config file. See [Dates and Times]({{< relref "ChordPro-Configuration-Generic#dates-and-times" >}}). - * `tuning`: The tuning of the instrument. Usually `"E2 A2 D3 G3 B3 E4"`. + * `tuning`: The tuning of the instrument. + For the default guitar config this is `"E2 A2 D3 G3 B3 E4"`. - * `instrument.type`: The name of the instrument as set by instrument - configs. Usually `"guitar"`. - - * `instrument`: Short for `instrument.type`. - - * `instrument.description`: Set by instrument configs. Usually `"Guitar, 6 - strings, standard tuning"`. - - * `user.name`: The (login) name of the user running ChordPro. - Initial value is derived from the environment. - * `user`: Short for `user.name`. * `user.fullname`: The full name of the user running ChordPro. Initial value is derived from the environment, if possible. -The value of `"instrument"` and `"user"` can be used for [directive + * `user.name`: The (login) name of the user running ChordPro. + Initial value is derived from the environment. + +The values of `"instrument"` and `"user"` can be used for [directive selection]({{< relref "chordpro-directives#conditional-directives" >}}) + +## Additional meta data for CSV generation + +See [Configuration for CSV output]({{< relref "chordpro-configuration-csv" >}}). + + * `pagerange`: The pages of the song, either a single page number or + a range like `3-7`. diff --git a/docs/content/ChordPro-Configuration-Generic.md b/docs/content/ChordPro-Configuration-Generic.md index 2513b062..ad0d4e78 100644 --- a/docs/content/ChordPro-Configuration-Generic.md +++ b/docs/content/ChordPro-Configuration-Generic.md @@ -63,6 +63,9 @@ These settings control global behaviour of the ChordPro program and can be chang "chords-canonical" : false, // If false, chorus labels are used as tags. "choruslabels" : true, + // Substitute Unicode sharp/flats in chord names. + // Will fallback to ChordProSymbols the font doesn't have the glyphs. + "truesf" : false, }, Note that settings `decapo`, `lyrics-only`, `strict`, `transcode` and diff --git a/docs/content/ChordPro-Configuration-Instrument.md b/docs/content/ChordPro-Configuration-Instrument.md index 269fcbf3..3412595a 100644 --- a/docs/content/ChordPro-Configuration-Instrument.md +++ b/docs/content/ChordPro-Configuration-Instrument.md @@ -89,13 +89,13 @@ This is a variant of Dutch where `H` is used instead of `B`, and `B` is used instead of `B♭`. Flats and sharps are denoted by `is` and `es` suffixes, not symbols. This definition is contained in the preset configuration -`notes_german`. +`notes:german`. * Scandinavian This is a variant of German where `H` means `B`, and `B♭` means B flat. Flats and sharps are denoted by the appropriate symbols. This definition is contained in the preset configuration -`notes_scandinavian`. +`notes:scandinavian`. * Latin This system consists of the diatonic note names `Do`, `Re`, `Mi`, @@ -103,7 +103,7 @@ This system consists of the diatonic note names `Do`, `Re`, `Mi`, appropriate symbols. It is often used in Italian, French, Spanish and Portuguese speaking countries. This definition is contained in the preset configuration -`notes_latin`. +`notes:latin`. For more information, see [Key signature names and translations](https://en.wikipedia.org/wiki/Key_signature_names_and_translations) on Wikipedia. diff --git a/docs/content/ChordPro-Configuration-Overview.md b/docs/content/ChordPro-Configuration-Overview.md index cd008f11..36a44980 100644 --- a/docs/content/ChordPro-Configuration-Overview.md +++ b/docs/content/ChordPro-Configuration-Overview.md @@ -126,7 +126,7 @@ The config files are processed in order, and their contents are merged. In gener ### Merging instrument definitions -Instrument definitions, in particular the settings `"tuning"`, `"notes"` and `"chords"`, are handled differently. These are processed immedeately after parsing a configuration file and then the setting is removed from the configuration. +Instrument definitions, in particular the settings `"tuning"`, `"notes"` and `"chords"`, are handled differently. These are processed immediately after parsing a configuration file and then the setting is removed from the configuration. For example, assume `"chords_italian.json"` defines a number of chords using italian (latin) note names and `"chords_german.json"` defines some chords using german note names. Then the following sequence of configuration files will work as expected: diff --git a/docs/content/ChordPro-Configuration-PDF.md b/docs/content/ChordPro-Configuration-PDF.md index bec2408c..c9d88442 100644 --- a/docs/content/ChordPro-Configuration-PDF.md +++ b/docs/content/ChordPro-Configuration-PDF.md @@ -113,13 +113,23 @@ This controls the distance between lines as a factor of the font size. "title" : 1.2, "lyrics" : 1.2, "chords" : 1.2, + "diagramchords" : 1.2, "grid" : 1.2, "tab" : 1.0, "toc" : 1.4, "empty" : 1.0, }, -Note: By setting the spacing for `empty` to a small value, you get fine-grained control over the spacing between the various parts of the song. +`lyrics` controls the spacing between songlines (chords + lyrics). + +`chords` controls the spacing between chords and lyrics in songlines. + +`diagramchords` controls the spacing between the chordname and the +diagram in chord diagrams. + +By setting the spacing for `empty` to a small value, you get +fine-grained control over the spacing between the various parts of the +song. ## Labels @@ -331,8 +341,9 @@ ChordPro distinguishes three types of output pages: * the first page of a song: `title`; * all other pages: `default`. -Each of these page types can have settings for a page title, subtitle -and footer. The settings inherit from `default` to `title` to `first`. +Each of these page types can have settings for a page title, subtitle, +footer, and background. +The settings inherit from `default` to `title` to `first`. So a `title` page has everything a `default` page has, and a `first` page has everything a `title` page has. @@ -350,6 +361,12 @@ obtained with `%{page}`, and the song index in the songbook with heading strings, see [here]({{< relref "ChordPro-Configuration-Format-Strings" >}}). +`background` can be used to designate an existing PDF document to be +used as background. It has the form _filename_ or _filename:page_. +Page numbers count from one. If odd/even printing is in effect, the +designated page number is used for left pages, and the next page (if +it exists) for right pages. + "formats" : { // By default, a page has: @@ -359,6 +376,8 @@ heading strings, see [here]({{< relref "subtitle" : null, // Footer is title -- page number. "footer" : [ "%{title}", "", "%{page}" ], + // Background pages: 5 and 6 from bgdemo. + "background" : "examples/bgdemo.pdf:5", }, // The first page of a song has: @@ -368,6 +387,8 @@ heading strings, see [here]({{< relref "subtitle" : [ "", "%{subtitle}", "" ], // Footer with page number. "footer" : [ "", "", "%{page}" ], + // Background pages: 3 and 4 from bgdemo. + "background" : "examples/bgdemo.pdf:3", }, // The very first output page is slightly different: @@ -375,24 +396,28 @@ heading strings, see [here]({{< relref // It has title and subtitle, like normal 'first' pages. // But no footer. "footer" : null, + // Background pages: 1 and 2 from bgdemo. + "background" : "examples/bgdemo.pdf:1", }, }, -The effect of the default settings can be seen in the following +The effect of the above settings can be seen in the following picture. ![]({{< asset "images/pageformats.png" >}}) -Pages 2 and 4 are normal (`default`) pages. They have no heading and -have the page number and song title in the footer. - -Page 3 is the first page of a song (`title`). It has the song title -and subtitle in the heading, and only the page number in the footer. - -Page 1 is the very first output page (`first`). It is like a `title` +Page 1 is the very first output page (type `first`). It is like a `title` page but, according to typesetting conventions, doesn't have the page number in the footer. +Page 4 is the first page of a song, but not the very first (type `title`). +It has the song title and subtitle in the heading, and only the page +number in the footer. + +The other pages are normal pages (type `default`). They have no heading and +have the page number and song title in the footer. Pages inserted for +alignment are completely blank. + Note that by default ChordPro produces different odd and even pages. Therefore the page number on (odd) page 3 is at the left side, while it is at the right side on (even) pages 2 and 4. diff --git a/docs/content/ChordPro-Directives.md b/docs/content/ChordPro-Directives.md index 797ae020..2c12cf4c 100644 --- a/docs/content/ChordPro-Directives.md +++ b/docs/content/ChordPro-Directives.md @@ -148,7 +148,12 @@ directive with a dash (hyphen) and a _selector_. If a selector is used, ChordPro first tries to match it with the instrument type (as defined in the [config file]({{< relref "chordpro-configuration-generic#instrument-description" >}})). If this fails, it -tries to match it with the user name (as defined in the [config file]({{< relref "chordpro-configuration-generic#user" >}})). +tries to match it with the user name (as defined in the [config +file]({{< relref "chordpro-configuration-generic#user" >}})). +Finally, it will try it as a meta item, selection will succeed if this +item exists and has a 'true' value (i.e., not empty, zero, `false` or +`null`). +Selection can be reversed by appending a `!` to the selector. For example, to define chords depending on the instrument used: diff --git a/docs/content/ChordPro-Reference-RelNotes.md b/docs/content/ChordPro-Reference-RelNotes.md index 65d56828..3f523990 100644 --- a/docs/content/ChordPro-Reference-RelNotes.md +++ b/docs/content/ChordPro-Reference-RelNotes.md @@ -1,18 +1,29 @@ # Release info -## 5.987 +## 5.988 -Released: 2022-02-08 +Released: 2022-05-17 ### ChordPro functionality +* Automatically use real sharps and flats in chord names. Fallback to the ChordProSymbols font if the font doesn't have the appropriate symbols. +* Add settings.truesf (default: false) to enable/disable this. +* Allow settings.* to be used in %{} substitutions. +* Add meta chords and numchords (list/number of chords used). +* Add config pdf.spacing.diagramchords. +* Allow meta values for directive selectors. +* Re-enable agnostic chord lookup. * (Wx)(MacOS) Improve prefences dialog. +* Several ABC fixes/improvements. +* (PDF) Add support for background document. +* Markdown export (EXPERIMENTAL). Thanks to Johannes Rumpf. +* LaTeX export (EXPERIMENTAL). Thanks to Johannes Rumpf. ### BugFixes -* Add File::HomeDir to dependencies. -* Fix issue #204. +* Fix issue #208. +* (Wx) Fix sharps/flats mixup in PreferencesDialog. ### Social and support @@ -20,6 +31,21 @@ Released: 2022-02-08 [Follow us on Twitter](https://twitter.com/ChordPro_Org) to stay informed about new releases and updates. +## 5.987 + +Released: 2022-02-08 + + +### ChordPro functionality + +* Conditional directives can be negated with a trailing ! +* (Wx)(MacOS) Improve prefences dialog. + +### BugFixes + +* Add File::HomeDir to dependencies. +* Fix issue #204. + ## 5.986 Released: 2022-02-02 diff --git a/docs/content/ChordPro6-RelNotes.md b/docs/content/ChordPro6-RelNotes.md index fda9da36..8c7a0389 100644 --- a/docs/content/ChordPro6-RelNotes.md +++ b/docs/content/ChordPro6-RelNotes.md @@ -116,6 +116,8 @@ For example: This will define the appropriate Dm chord for either ukulele or guitar. +Selection can be reversed by appending a `!` to the selector. + How selectors are defined depends on the ChordPro processing tool. The reference implementation uses the config values for `instrument.type` and `user.name`. diff --git a/docs/content/Support-Hints-And-Tips.md b/docs/content/Support-Hints-And-Tips.md index 2d4d56f4..d975f8a3 100644 --- a/docs/content/Support-Hints-And-Tips.md +++ b/docs/content/Support-Hints-And-Tips.md @@ -30,10 +30,28 @@ Secondly, wrap the chorus in `textfont` directives: {eoc} ```` -This might become easier in the future, -see https://github.com/ChordPro/chordpro/issues/174. +## Chords too close to the lyrics -## [Conditional chords](https://github.com/ChordPro/chordpro/issues/176) +The distance between the chords and the lyrics is determined by the +properties of the font used for the chords. Some fonts use the size of +the font as distance, which results in chords being placed too close, +and other fonts use the distance to the next line, resulting in chords +being higher above the lyrics. + +You can adjust the chord spacing in the PDF config: + +```` +{ "pdf" : + { "spacing" : + { "chords" : 1.2, + ... +```` + +The value specified is a factor, it is multiplied by the font size to +obtain the distance between baseline of the chords and the baseline of +the lyrics. + +## Conditional chords You can use the following preprocessor directive to suffix chords with an instrument name diff --git a/docs/content/Trouble-Shooting.md b/docs/content/Trouble-Shooting.md index f05cf1b6..eea0f863 100644 --- a/docs/content/Trouble-Shooting.md +++ b/docs/content/Trouble-Shooting.md @@ -53,3 +53,20 @@ When running from the GUI, check Help > Enable debug info in PDF and try again. _You will get a "Problems Found" dialog with diagnostic information, this can be ignored._. Save the PDF document and add it to be bug report. + +## ChordPro output looks okay, but ABC parts are missing or wrong + +To process ABC data, ChordPro relies on a couple of external tools: +`abcm2ps` and `convert`. These tools should be easily added to your +system if not already there. + +For `abcm2ps` version 8.12.14 or later is advised, although earlier +versions usually work okay in most cases. + +`convert` is part of ImageMagick. Version 7 is advised, but 6.9.12 or +later will also work okay in most cases. For `convert` it is +imperative that it can handle SVG image files. Some system vendors +find it necessary to build ImageMagick without SVG support in which +case you'll get crippled or no output. + +See also https://github.com/ChordPro/chordpro/issues/217 . diff --git a/docs/layouts/_default/_markup/render-heading.html b/docs/layouts/_default/_markup/render-heading.html new file mode 100644 index 00000000..d8c2454f --- /dev/null +++ b/docs/layouts/_default/_markup/render-heading.html @@ -0,0 +1,6 @@ + + {{- if not ( eq hugo.Environment "stable" ) -}} + # + {{- end -}} + {{ .Text | safeHTML }} + diff --git a/lib/App/Music/ChordPro.pm b/lib/App/Music/ChordPro.pm index 651e8ec9..d3aa7881 100644 --- a/lib/App/Music/ChordPro.pm +++ b/lib/App/Music/ChordPro.pm @@ -124,6 +124,9 @@ sub chordpro { elsif ( $of =~ /\.mma?$/i ) { $options->{generate} ||= "MMA"; } + elsif ( $of =~ /\.(md|markdown)$/i ) { + $options->{generate} ||= "Markdown"; + } elsif ( $of =~ /\.(debug)$/i ) { $options->{generate} ||= "Debug"; } diff --git a/lib/App/Music/ChordPro/Chords.pm b/lib/App/Music/ChordPro/Chords.pm index 85e9d780..9b090b6b 100644 --- a/lib/App/Music/ChordPro/Chords.pm +++ b/lib/App/Music/ChordPro/Chords.pm @@ -298,7 +298,28 @@ sub pop_parser { sub _known_chord { my ( $name ) = @_; - $song_chords{$name} // $config_chords{$name}; + my $info; + if ( ref($name) =~ /^App::Music::ChordPro::Chord::/ ) { + $info = $name; + $name = $info->name; + } + my $ret = $song_chords{$name} // $config_chords{$name}; + return $ret if $ret || !$info; + + # Retry agnostic. + $name = $info->agnostic; + $ret = $song_chords{$name} // $config_chords{$name}; + if ( $ret ) { + $ret = $info->new($ret); + for ( qw( name display + root root_canon + bass bass_canon + system parser ) ) { + next unless defined $info->{$_}; + $ret->{$_} = $info->{$_}; + } + } + $ret; } sub _check_chord { diff --git a/lib/App/Music/ChordPro/Chords/Parser.pm b/lib/App/Music/ChordPro/Chords/Parser.pm index 14b786f0..43edeeef 100644 --- a/lib/App/Music/ChordPro/Chords/Parser.pm +++ b/lib/App/Music/ChordPro/Chords/Parser.pm @@ -929,12 +929,32 @@ sub transcode { } sub chord_display { - my ( $self, $raw ) = @_; - my $res = $self->{display} - ? $raw - ? $self->{display} - : interpolate( { args => $self }, $self->{display} ) - : $self->show("np"); + my ( $self, $sf ) = @_; + + my $res = + $self->{display} + ? interpolate( { args => $self }, $self->{display} ) + : $self->show("np"); + + # Substitute musical symbols if wanted and possible. + if ( $::config->{settings}->{truesf} ) { + $sf ||= 0; + if ( $sf & 0x02 ) { # has flat + pos($res) = 1; + $res =~ s/b/♭/g; + } + else { # fallback + pos($res) = 1; + $res =~ s;b;!;g; + } + if ( $sf & 0x01 ) { # has sharp + $res =~ s/#/♯/g; + } + else { # fallback + $res =~ s;#;#;g; + } + } + return $self->{parens} ? "($res)" : $res; } diff --git a/lib/App/Music/ChordPro/Config.pm b/lib/App/Music/ChordPro/Config.pm index f4932ba9..cd3a7618 100644 --- a/lib/App/Music/ChordPro/Config.pm +++ b/lib/App/Music/ChordPro/Config.pm @@ -113,7 +113,8 @@ sub configurator { $cfg->{user}->{fullname} = ::runtimeinfo("short"); } else { - $cfg->{user}->{name} = $ENV{USER} || $ENV{LOGNAME} || lc(getlogin()); + $cfg->{user}->{name} = $ENV{USER} || $ENV{LOGNAME} + || lc(getlogin()) || getpwuid($<) || "chordpro"; $cfg->{user}->{fullname} = eval { (getpwuid($<))[6] } || ""; } @@ -961,6 +962,9 @@ sub default_config() { "chords-canonical" : false, // If false, chorus labels are used as tags. "choruslabels" : true, + // Substitute Unicode sharp/flats in chord names. + // Will fallback to ChordProSymbols the font doesn't have the glyphs. + "truesf" : false, }, // Metadata. @@ -1121,6 +1125,7 @@ sub default_config() { "handler" : "abc2image", "config" : "default", // or "none", or "myformat.fmt" "preamble" : [], + "preprocess" : { "abc" : [], "svg" : [] }, }, "ly" : { "type" : "image", @@ -1185,6 +1190,7 @@ sub default_config() { "title" : 1.2, "lyrics" : 1.2, "chords" : 1.2, + "diagramchords" : 1.2, "grid" : 1.2, "tab" : 1.0, "toc" : 1.4, @@ -1295,7 +1301,8 @@ sub default_config() { "pagealign-songs" : 1, // Formats. - // Pages have two title elements and one footer element. + // Pages have two title elements and one footer element. They also + // can have a page of an existing PDF file as underlay (background). // Topmost is "title". It uses the "title" font as defined further below. // Second is "subtitle". It uses the "subtitle" font. // The "footer" uses the "footer" font. @@ -1311,10 +1318,12 @@ sub default_config() { // title/subtitle fields. Don't try to add an artist page element. "formats" : { - // Titles/Footers. + // Titles/Footers. - // Titles/footers have 3 parts, which are printed left, + // Titles/footers have 3 parts, which are printed left, // centered and right. + // For odd/even printing, the 1st background page is used + // for left pages and the next page (if it exists) for right pages. // For even/odd printing, the order is reversed. // By default, a page has: @@ -1324,6 +1333,8 @@ sub default_config() { "subtitle" : [ "", "", "" ], // Footer is title -- page number. "footer" : [ "%{title}", "", "%{page}" ], + // Background page. + "background" : "", }, // The first page of a song has: "title" : { @@ -1332,12 +1343,16 @@ sub default_config() { "subtitle" : [ "", "%{subtitle}", "" ], // Footer with page number. "footer" : [ "", "", "%{page}" ], + // Background page. + "background" : "", }, // The very first output page is slightly different: "first" : { // It has title and subtitle, like normal 'first' pages. // But no footer. "footer" : [ "", "", "" ], + // Background page. + "background" : "", }, }, @@ -1533,6 +1548,16 @@ sub default_config() { }, }, + // Settings for LaTeX backend. + "latex" : { + "template_include_path" : [ ], + "templates" : { + "songbook" : "songbook.tt", + "comment" : "comment.tt", + "image" : "image.tt" + } + }, + // Settings for Text backend. "text" : { // Style of chorus. diff --git a/lib/App/Music/ChordPro/Delegate/ABC.pm b/lib/App/Music/ChordPro/Delegate/ABC.pm index 2dc84759..3baca73f 100644 --- a/lib/App/Music/ChordPro/Delegate/ABC.pm +++ b/lib/App/Music/ChordPro/Delegate/ABC.pm @@ -20,11 +20,16 @@ use Text::ParseWords qw(shellwords); sub DEBUG() { $config->{debug}->{abc} } +# ABC processing using abcm2ps and ImageMagick. + sub abc2image { - my ( $s, $pr, $elt ) = @_; + my ( $s, $pw, $elt ) = @_; state $imgcnt = 0; state $td = File::Temp::tempdir( CLEANUP => !$config->{debug}->{abc} ); + my $cfg = $config->{delegates}->{abc}; + + my $prep = make_preprocessor( $cfg->{preprocess} ); $imgcnt++; my $src = File::Spec->catfile( $td, "tmp${imgcnt}.abc" ); @@ -43,14 +48,13 @@ sub abc2image { # Suppress meaningless transpositions. ChordPro uses them to enforce # certain chord renderings. - next if $_ eq "transpose" - && !($elt->{opts}->{$_} % @{ $config->{notes}->{sharp} }); - - print $fd '%%'.$_." ".$elt->{opts}->{$_}."\n"; - warn('%%'.$_." ".$elt->{opts}->{$_}."\n") if DEBUG; + next if $_ ne "transpose"; + my $x = $elt->{opts}->{$_} % @{ $config->{notes}->{sharp} }; + print $fd '%%transpose'." $x\n"; + warn('%%transpose'." $x\n") if DEBUG; } - for ( @{ $config->{delegates}->{abc}->{preamble} } ) { + for ( @{ $cfg->{preamble} } ) { print $fd "$_\n"; warn( "$_\n") if DEBUG; } @@ -72,6 +76,7 @@ sub abc2image { $kv = parse_kv( @pre ) if @pre; # Copy. We assume the user knows how to write ABC. for ( @data ) { + $prep->{abc}->($_) if $prep->{abc}; print $fd $_, "\n"; warn($_, "\n") if DEBUG; } @@ -81,17 +86,6 @@ sub abc2image { return; } - # Available width and height. - my $pw; - my $ps = $pr->{ps}; - if ( $ps->{columns} > 1 ) { - $pw = $ps->{columnoffsets}->[1] - - $ps->{columnoffsets}->[0] - - $ps->{columnspace}; - } - else { - $pw = $ps->{__rightmargin} - $ps->{_leftmargin}; - } if ( $kv->{width} ) { $pw = $kv->{width}; } @@ -119,7 +113,7 @@ sub abc2image { my $svg0 = File::Spec->catfile( $td, "tmp${imgcnt}.svg" ); my $svg1 = File::Spec->catfile( $td, "tmp${imgcnt}001.svg" ); - my $fmt = $config->{delegates}->{abc}->{config}; + my $fmt = $cfg->{config}; my @cmd = ( $abcm2ps, qw(-g -q -m0cm), "-w" . $pw . "pt" ); if ( $fmt =~ s/^none,?// ) { push( @cmd, "+F" ); @@ -144,7 +138,10 @@ sub abc2image { # @lines = loadlines($svg1, { encoding => "ISO-8859-1" } ); @lines = loadlines($svg1); for ( @lines ) { + + $prep->{svg}->($_) if $prep->{svg}; next unless /^(.*)\bstyle="font:(.*)"(.*)$/; + my ( $pre, $style, $post ) = ( $1, $2, $3 ); my $f = {}; my @f; @@ -215,7 +212,7 @@ sub abc2image { $fn =~ s/\.svg$/.jpg/; $image->Set( magick => 'jpg' ); my $data = $image->ImageToBlob; - my $assetid = sprintf("ABCasset%03d", $imgcnt++); + my $assetid = $kv->{asset} || sprintf("ABCasset%03d", $imgcnt++); warn("Created asset $assetid (jpg, ", length($data), " bytes)\n") if $config->{debug}->{images}; $App::Music::ChordPro::Output::PDF::assets->{$assetid} = @@ -226,7 +223,7 @@ sub abc2image { uri => "id=$assetid", opts => { center => $kv->{center}, scale => $kv->{scale} * 0.16 } }, { type => "empty" }, - ); + ) unless $kv->{asset}; }; while ( @lines ) { @@ -299,7 +296,7 @@ sub abc2image { my $data = do { local $/; <$im> }; close($im); - my $assetid = sprintf("ABCasset%03d", $imgcnt); + my $assetid = $kv->{asset} || sprintf("ABCasset%03d", $imgcnt); warn("Created asset $assetid (jpg, ", length($data), " bytes)\n") if $config->{debug}->{images}; $App::Music::ChordPro::Output::PDF::assets->{$assetid} = @@ -308,7 +305,431 @@ sub abc2image { push( @res,{ type => "image", uri => "id=$assetid", opts => { center => $kv->{center}, scale => $kv->{scale} * 0.16 } }, - ); + ) unless $kv->{asset}; + warn("Asset $assetid options:", + " scale=", $kv->{scale} * 0.16, + " center=", $kv->{center}//0, + "\n") + if $config->{debug}->{images}; + } + + + return \@res; +} + +# ABC processing using abc2svg and Chrome and ImageMagick. +# FOR EXPERIMENTAL PURPOSES ONLY! + +sub xabc2image { + my ( $s, $pw, $elt ) = @_; + + state $imgcnt = 0; + state $td = File::Temp::tempdir( CLEANUP => !$config->{debug}->{abc} ); + my $cfg = $config->{delegates}->{abc}; + + state $abc2svg = findexe("abc2svg"); # not yet + state $chrome; + unless ( $abc2svg ) { + if ( is_msw() and my $x = findexe("npx.cmd") ) { + $abc2svg = [ $x, "abc2svg" ]; + } + } + unless ( $chrome ) { + ####TODO: MacOS + for ( "chromium-freeworld", "chromium", "google-chrome" ) { + last if $chrome = findexe($_); + } + if ( !$chrome && is_msw() ) { + $chrome = 'C:\Program Files\Google\Chrome\Application\chrome.exe'; + undef $chrome unless -x $chrome; + } + warn("Using \"$chrome\" for SVG processing\n"); + } + state $abcm2ps = findexe("abcm2ps"); + unless ( $abcm2ps || $abc2svg ) { + #warn("Error in ABC embedding: need 'abcm2ps' or 'abc2svg' tool.\n"); + warn("Error in ABC embedding: need 'abcm2ps' tool.\n"); + return; + } + + my $prep = make_preprocessor( $cfg->{preprocess} ); + + $imgcnt++; + my $src = File::Spec->catfile( $td, "tmp${imgcnt}.abc" ); + my $img = File::Spec->catfile( $td, "tmp${imgcnt}.jpg" ); + if ( $elt->{subtype} =~ /^image-(\w+)$/ ) { + $img = File::Spec->catfile( $td, "tmp${imgcnt}.$1" ); + } + + my $fd; + unless ( open( $fd, '>:utf8', $src ) ) { + warn("Error in ABC embedding: $src: $!\n"); + return; + } + + if ( $abc2svg ) { + my $f = ::rsc_or_file( "fonts/abc2svg.ttf" ); + for ( '%%fullsvg a', "%%musicfont abc2svg" ) { + print $fd "$_\n"; + warn( "$_\n") if DEBUG; + } + } + + my @preamble = @{ $cfg->{preamble} }; + + for ( keys(%{$elt->{opts}}) ) { + + # Suppress meaningless transpositions. ChordPro uses them to enforce + # certain chord renderings. + next if $_ ne "transpose"; + my $x = $elt->{opts}->{$_} % @{ $config->{notes}->{sharp} }; + unshift( @preamble, '%%transpose'." $x" ); + } + + # Add mandatory field. + my @pre; + my @data = @{$elt->{data}}; + while ( @data ) { + $_ = shift(@data); + unshift( @data, $_ ), last if /^X:/; + push( @pre, $_ ); + } + if ( @pre && !@data ) { # no X: found + warn("X:1 (added)\n") if DEBUG; + @data = ( "X:1", @pre ); + @pre = (); + } + my $kv = { %$elt }; + $kv = parse_kv( @pre ) if @pre; + $kv->{split} = 1 if $abc2svg; + + if ( $kv->{width} ) { + $pw = $kv->{width}; + } + my $have_magick = do { + local $SIG{__WARN__} = sub {}; + local $SIG{__DIE__} = sub {}; + eval { require Image::Magick; + $Image::Magick::VERSION || "6.x?" }; + }; + if ( $have_magick ) { + warn("Using PerlMagick version ", $have_magick, "\n") + if $config->{debug}->{images} || DEBUG; + } + else { + warn("No PerlMagick, hope you have ImageMagick installed...\n") + if $config->{debug}->{images} || DEBUG; + $kv->{split} = 0; + } + + if ( $kv->{split} && !$abc2svg ) { + unshift( @preamble, '%%fullsvg a' ); + } + unshift( @preamble, + "%%pagewidth " . $pw . "pt", + "%%leftmargin 0cm", + "%%rightmargin 0cm", + ); + + # Copy. We assume the user knows how to write ABC. + for ( @preamble ) { + print $fd $_, "\n"; + warn($_, "\n") if DEBUG; + } + for ( @data ) { + $prep->{abc}->($_) if $prep->{abc}; + print $fd $_, "\n"; + warn($_, "\n") if DEBUG; + } + + unless ( close($fd) ) { + warn("Error in ABC embedding: $src: $!\n"); + return; + } + + my $svg0 = File::Spec->catfile( $td, "tmp${imgcnt}.svg" ); + my $svg1 = File::Spec->catfile( $td, "tmp${imgcnt}001.svg" ); + + if ( $abc2svg ) { + my @cmd = ref($abc2svg) ? ( @$abc2svg ) : ( $abc2svg ); + open( my $STDOLD, '>&', STDOUT ); + open( STDOUT, '>:utf8', $svg1 ); + push( @cmd, $src ); + warn( "+ @cmd\n" ) if DEBUG; + my $ret = sys( @cmd ); + open( STDOUT, '>&', $STDOLD ); + if ( $ret or ! -s $svg1 ) { + warn("Error in ABC embedding\n"); + return; + } + } + else { + my $fmt = $cfg->{config}; + my @cmd = ( $abcm2ps, qw(-g -q) ); + if ( $fmt =~ s/^none,?// ) { + push( @cmd, "+F" ); + } + push( @cmd, "-F", $fmt ) if $fmt && $fmt ne "default"; + push( @cmd, "-A" ) if $kv->{split}; + push( @cmd, "-O", $svg0, $src ); + warn( "+ @cmd\n" ) if DEBUG; + if ( sys( @cmd ) + or + ! -s $svg1 ) { + warn("Error in ABC embedding\n"); + return; + } + } + $kv->{scale} ||= 1; + + my @res; + my @lines; + if ( 1 ) { +# @lines = loadlines($svg1, { encoding => "ISO-8859-1" } ); + @lines = loadlines($svg1); + my @lp; + for ( @lines ) { + + # =for abc2svg + # s|src:url\("data:application/octet-stream;base64,.* format\("truetype"\)|src:url("abc2svg.ttf")| and next; + + # s;^(\.f\d+\{.*?px) music\};$1 abc2svg}; and next; + + # abc2svg generates text elements with multiple x,y coordinates. + # librsvg cannot handle these, so split them out. + if ( /(.*?); ) { # combine + my @t = split(//, $1); + warn("@t / @{ $lp[0] } / @{ $lp[1] }") + unless @t == @{ $lp[0] }; + $_ = ""; + for my $c ( @t ) { + $_ .= "\n" if $_; + $_ .= "" . $c . ""; + } + @lp = (); + next; + } + # =end abc2svg + + # Preprocessing. + $prep->{svg}->($_) if $prep->{svg}; + + # =for abcm2ps + # Sigh. ImageMagick uses librsvg, and this lib still does not + # support font styles. So replace them with their explicit forms. + next unless /^(.*)\bstyle="font:(.*)"(.*)$/; + + my ( $pre, $style, $post ) = ( $1, $2, $3 ); + my $f = {}; + my @f; + for my $w ( shellwords($style) ) { + if ( $w =~ /^(bold|light)$/ ) { + $f->{weight} = $1; + } + elsif ( $w =~ /^(italic|oblique)$/ ) { + $f->{style} = $1; + } + elsif ( $w =~ /^(\d+(?:\.\d*)?)px$/ ) { + $f->{size} = 0+$1; + } + else { + push( @f, $w ); + } + } + $f->{family} = @f ? "@f" : "Serif"; + + if ( 0 && is_msw() ) { + # Windows doesn't seem to find the right fonts. + # So lend a hand. + $f->{family} = "Times New Roman" if $f->{family} eq "Times"; + $f->{family} = "Arial" if $f->{family} eq "Helvetica"; + $f->{family} = "Courier New" if $f->{family} eq "Courier"; + } + + $_ = $pre; + $_ .= "font-family=\"" . $f->{family} . '" '; + $_ .= "font-size=\"" . $f->{size} . '" ' if $f->{size}; + $_ .= "font-weight=\"" . $f->{weight} . '" ' if $f->{weight}; + $_ .= $post; + warn("\"${pre}style=\"font:$style\"$post\" => \"$_\"\n") + if DEBUG; + # =end abcm2ps + } + + unless ( $kv->{split} ) { + open( my $fd, '>:utf8', $svg1 ) + or die("Cannot rewrite $svg1: $!\n"); + print $fd ( "$_\n" ) for @lines; + close($fd) or die("Error rewriting $svg1: $!\n");; + } + } + + if ( $kv->{split} ) { + require Image::Magick; + + my $segment = 0; + my $init = 1; + + my @preamble; + + my $fd; + my $fn; + + my $pp = sub { + print $fd "\n"; + close($fd); + + warn("Processing split$segment \"$fn\"\n") if DEBUG; + if ( $chrome ) { + my $f = $fn; + $f =~ s/\.svg$/.png/; + sys( $chrome, "--headless", "--disable-gpu", + "--screenshot=$f", "--force-device-scale-factor=8.333", + $fn ); + die("Error converting \"$fn\" tp \"$f\" using \"$chrome\"\n") + unless -s $f; + $fn = $f; + } + my $image = Image::Magick->new( density => 600, background => 'white' ); + warn("Reading $fn...\n") if $config->{debug}->{images}; + my $x = $image->Read($fn); + warn $x if $x; + $x = $image->Trim; + warn $x if $x; + warn("Trim: ", join("x", $image->Get('width', 'height')). + " ", join("x", $image->Get('base-columns', 'base-rows')), + "+", join("+", $image->Get('page.x', 'page.y')), "\n") + if $config->{debug}->{images}; + $fn =~ s/\.svg$/.jpg/; + $image->Set( magick => 'jpg' ); + my $data = $image->ImageToBlob; + my $assetid = $kv->{asset} || sprintf("ABCasset%03d", $imgcnt++); + warn("Created asset $assetid (jpg, ", length($data), " bytes)\n") + if $config->{debug}->{images}; + $App::Music::ChordPro::Output::PDF::assets->{$assetid} = + { type => "jpg", data => $data }; + + push( @res, + { type => "image", + uri => "id=$assetid", + opts => { center => $kv->{center}, scale => $kv->{scale} * 0.16 } }, + { type => "empty" }, + ) unless $kv->{asset}; + }; + + my $skip = $abc2svg; + while ( @lines ) { + $_ = shift(@lines); + + if ( $skip && /^catfile( $td, sprintf( "out%03d.svg", ++$segment ) ); + warn("Writing: $fn ...\n") if $config->{debug}->{images}; + undef $fd; + open( $fd, '>:utf8', $fn ) or die("$fn: $!\n"); + print $fd ( "$_\n" ); + $init = 0; + next; + } + else { + next if $skip; + } + + if ( /^<(style|defs)\b/ ) { + $init = 0; + push( @preamble, $_ ); + print $fd "$_\n" if $segment; + while ( @lines ) { + push( @preamble, $lines[0] ); + print $fd "$lines[0]\n" if $segment; + last if shift @lines eq ""; + } + next; + } + if ( $init ) { + push( @preamble, $_ ); + print $fd "$_\n" if $segment; + next; + } + if ( /^ 8 + && $lines[0] =~ /^() if $fd; + $fn = File::Spec->catfile( $td, sprintf( "out%03d.svg", ++$segment ) ); + warn("Writing: $fn ...\n") if $config->{debug}->{images}; + undef $fd; + open( $fd, '>:utf8', $fn ) or die("$fn: $!\n"); + print $fd ( "$_\n" ) for @preamble; + } + + if ( $fd && $abc2svg && /<\/svg>/ ) { + $pp->(); + $fn = File::Spec->catfile( $td, sprintf( "out%03d.svg", ++$segment ) ); + warn("Writing: $fn ...\n") if $config->{debug}->{images}; + undef $fd; + open( $fd, '>:utf8', $fn ) or die("$fn: $!\n"); + next; + } + last if /<\/svg>/; + print $fd ("$_\n") if $fd; + } + + $pp->() if $fd && !$abc2svg; + pop(@res); + } + else { # no split, so no abc2svg + my @cmd; + if ( is_msw() ) { + state $magick = findexe("magick"); + unless ( $magick ) { + warn("Error in ABC embedding: missing 'imagemagick/convert' tool.\n"); + return; + } + @cmd = ( $magick, "convert" ); + } + else { + state $convert = findexe("convert"); + unless ( $convert ) { + warn("Error in ABC embedding: missing 'imagemagick/convert' tool.\n"); + return; + } + @cmd = ( $convert ); + } + push( @cmd, qw(-density 600 -background white -trim), $svg1, $img ); + warn( "+ @cmd\n" ) if DEBUG; + if ( sys( @cmd ) ) { + warn("Error in ABC embedding\n"); + return; + } + + warn("Reading $img...\n") if $config->{debug}->{images}; + open( my $im, '<:raw', $img ); + my $data = do { local $/; <$im> }; + close($im); + + my $assetid = $kv->{asset} || sprintf("ABCasset%03d", $imgcnt); + warn("Created asset $assetid (jpg, ", length($data), " bytes)\n") + if $config->{debug}->{images}; + $App::Music::ChordPro::Output::PDF::assets->{$assetid} = + { type => "jpg", data => $data }; + + push( @res,{ type => "image", + uri => "id=$assetid", + opts => { center => $kv->{center}, scale => $kv->{scale} * 0.16 } }, + ) unless $kv->{asset}; warn("Asset $assetid options:", " scale=", $kv->{scale} * 0.16, " center=", $kv->{center}//0, @@ -322,6 +743,20 @@ sub abc2image { 1; +# chromium-freeworld --headless --disable-gpu --screenshot x1.svg +# +# "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --screenshot --default-background-color=0 image.svg +# +# 'C:\Program Files\Google\Chrome\Application\chrome.exe' --headless --screenshot=fullpathto.png --default-background-color=0 fulpathtoimage.svg + +# Install node,js +# open cmd window +# npx abc2svg +# (will install abc2svg) +# Install the abc2svg.ttf font in c:\Windows\Fonts +# (Add custom fonts, e.g. MuseJazzText.otf as well) +# Install Google Chrome + # =for later_maybe # # # abcm2ps -> SVG -> rsvg-convert -> PNG. NO TRIM. @@ -356,3 +791,125 @@ sub abc2image { # } # # =cut + +# ABC processing using abc2svg and custom SVG processor. +# FOR EXPERIMENTAL PURPOSES ONLY! + +sub abc2svg { + my ( $s, $pw, $elt ) = @_; + + state $imgcnt = 0; + state $td = File::Temp::tempdir( CLEANUP => !$config->{debug}->{abc} ); + my $cfg = $config->{delegates}->{abc}; + + state $abc2svg = findexe("abc2svg"); + unless ( $abc2svg ) { + my $x; + if ( $x = findexe("npx") + or is_msw() and $x = findexe("npx.cmd") ) { + $abc2svg = [ $x, "abc2svg" ]; + } + } + + unless ( $abc2svg ) { + warn("Error in ABC embedding: need 'abc2svg' tool.\n"); + return; + } + + my $prep = make_preprocessor( $cfg->{preprocess} ); + + $imgcnt++; + my $src = File::Spec->catfile( $td, "tmp${imgcnt}.abc" ); + my $svg = File::Spec->catfile( $td, "tmp${imgcnt}.xhtml" ); + + my $fd; + unless ( open( $fd, '>:utf8', $src ) ) { + warn("Error in ABC embedding: $src: $!\n"); + return; + } + + if ( $abc2svg ) { + my $f = ::rsc_or_file( "fonts/abc2svg.ttf" ); + # Currently we have a dup id when using fullsvg. + for ( "%%musicfont abc2svg" ) { + print $fd "$_\n"; + warn( "$_\n") if DEBUG; + } + } + + my @preamble = @{ $cfg->{preamble} }; + + for ( keys(%{$elt->{opts}}) ) { + + # Suppress meaningless transpositions. ChordPro uses them to enforce + # certain chord renderings. + next if $_ ne "transpose"; + my $x = $elt->{opts}->{$_} % @{ $config->{notes}->{sharp} }; + unshift( @preamble, '%%transpose'." $x" ); + } + + # Add mandatory field. + my @pre; + my @data = @{$elt->{data}}; + while ( @data ) { + $_ = shift(@data); + unshift( @data, $_ ), last if /^X:/; + push( @pre, $_ ); + } + if ( @pre && !@data ) { # no X: found + warn("X:1 (added)\n") if DEBUG; + @data = ( "X:1", @pre ); + @pre = (); + } + my $kv = { %$elt }; + $kv = parse_kv( @pre ) if @pre; + $kv->{split} = 1 if $abc2svg; + $kv->{scale} ||= 1; + if ( $kv->{width} ) { + $pw = $kv->{width}; + } + + unshift( @preamble, + "%%pagewidth " . $pw . "px", + "%%leftmargin 0cm", + "%%rightmargin 0cm", + ); + + # Copy. We assume the user knows how to write ABC. + for ( @preamble ) { + print $fd $_, "\n"; + warn($_, "\n") if DEBUG; + } + for ( @data ) { + $prep->{abc}->($_) if $prep->{abc}; + print $fd $_, "\n"; + warn($_, "\n") if DEBUG; + } + + unless ( close($fd) ) { + warn("Error in ABC embedding: $src: $!\n"); + return; + } + + my @cmd = ref($abc2svg) ? ( @$abc2svg ) : ( $abc2svg ); + open( my $STDOLD, '>&', STDOUT ); + open( STDOUT, '>:utf8', $svg ); + push( @cmd, "toxhtml.js", $src ); + warn( "+ @cmd\n" ) if DEBUG; + my $ret = sys( @cmd ); + open( STDOUT, '>&', $STDOLD ); + if ( $ret or ! -s $svg ) { + warn("Error in ABC embedding\n"); + return; + } + + my @res; + push( @res, + { type => "svg", + uri => $svg, + opts => { center => $kv->{center}, scale => $kv->{scale} } } ); + + return \@res; +} + +1; diff --git a/lib/App/Music/ChordPro/Delegate/Lilypond.pm b/lib/App/Music/ChordPro/Delegate/Lilypond.pm index 7eb1debc..9064269d 100644 --- a/lib/App/Music/ChordPro/Delegate/Lilypond.pm +++ b/lib/App/Music/ChordPro/Delegate/Lilypond.pm @@ -21,7 +21,7 @@ use Text::ParseWords qw(shellwords); sub DEBUG() { $config->{debug}->{ly} } sub ly2image { - my ( $s, $pr, $elt ) = @_; + my ( $s, $pw, $elt ) = @_; state $imgcnt = 0; state $td = File::Temp::tempdir( CLEANUP => !$config->{debug}->{ly} ); @@ -90,17 +90,6 @@ sub ly2image { return; } - # Available width and height. - my $pw; - my $ps = $pr->{ps}; - if ( $ps->{columns} > 1 ) { - $pw = $ps->{columnoffsets}->[1] - - $ps->{columnoffsets}->[0] - - $ps->{columnspace}; - } - else { - $pw = $ps->{__rightmargin} - $ps->{_leftmargin}; - } if ( $kv->{width} ) { $pw = $kv->{width}; } @@ -164,7 +153,7 @@ sub ly2image { $image->Set( magick => 'jpg' ); my $data = $image->ImageToBlob; - my $assetid = sprintf("LYasset%03d", $imgcnt++); + my $assetid = $kv->{asset} || sprintf("LYasset%03d", $imgcnt++); warn("Created asset $assetid (jpg, ", length($data), " bytes)\n") if $config->{debug}->{images}; $App::Music::ChordPro::Output::PDF::assets->{$assetid} = @@ -177,7 +166,7 @@ sub ly2image { $kv->{scale} ? ( scale => $kv->{scale} * 0.16 ) : (), } }, { type => "empty" }, - ); + ) unless $kv->{asset}; warn("Asset $assetid options:", $kv->{scale} ? ( " scale=", $kv->{scale} * 0.16 ) : (), " center=", $kv->{center}//0, @@ -195,7 +184,7 @@ sub ly2image { my $data = do { local $/; <$im> }; close($im); - my $assetid = sprintf("LYasset%03d", $imgcnt); + my $assetid = $kv->{asset} || sprintf("LYasset%03d", $imgcnt); warn("Created asset $assetid (png, ", length($data), " bytes)\n") if $config->{debug}->{images}; $App::Music::ChordPro::Output::PDF::assets->{$assetid} = @@ -206,7 +195,7 @@ sub ly2image { opts => { center => $kv->{center}, $kv->{scale} ? ( scale => $kv->{scale} * 0.16 ) : (), } }, - ); + ) unless $kv->{asset}; warn("Asset $assetid options:", $kv->{scale} ? ( " scale=", $kv->{scale} * 0.16 ) : (), " center=", $kv->{center}//0, diff --git a/lib/App/Music/ChordPro/Output/ChordPro.pm b/lib/App/Music/ChordPro/Output/ChordPro.pm index 8a2712a9..4f84a588 100644 --- a/lib/App/Music/ChordPro/Output/ChordPro.pm +++ b/lib/App/Music/ChordPro/Output/ChordPro.pm @@ -94,7 +94,7 @@ sub generate_song { # Unknowns with meta prefix. foreach my $k ( sort keys %{ $s->{meta} } ) { next if $used{$k}; - next if $k =~ /^(?:title|subtitle|songindex|key_.*)$/; + next if $k =~ /^(?:title|subtitle|songindex|key_.*|chords|numchords)$/; next if $k =~ /^_/; push( @s, map { +"{meta: $k $_}" } @{ $s->{meta}->{$k} } ); } @@ -103,6 +103,8 @@ sub generate_song { if ( $s->{settings} ) { foreach ( sort keys %{ $s->{settings} } ) { push(@s, "{$_: " . $s->{settings}->{$_} . "}"); + $s[-1] = "{no_grid}" + if $s[-1] eq "{diagrams: 0}"; } } diff --git a/lib/App/Music/ChordPro/Output/Common.pm b/lib/App/Music/ChordPro/Output/Common.pm index 28eac6f3..99000b26 100644 --- a/lib/App/Music/ChordPro/Output/Common.pm +++ b/lib/App/Music/ChordPro/Output/Common.pm @@ -49,6 +49,13 @@ sub fmt_subst { setlocale( LC_TIME, "" ); $m->{today} //= [ strftime( $config->{dates}->{today}->{format}, localtime(time) ) ]; + + for ( keys %{ $config->{settings} } ) { + my $v = $config->{settings}->{$_}; + $v = '' if $v =~ /^(0|false|off)$/i; + $v = 1 if $v=~ /^(true|on)$/i; + $m->{"settings.$_"} = $v; + } interpolate( { %$s, args => $m, separator => $config->{metadata}->{separator} }, $t ); diff --git a/lib/App/Music/ChordPro/Output/HTML.pm b/lib/App/Music/ChordPro/Output/HTML.pm index 13f6a56b..e10d4d4b 100644 --- a/lib/App/Music/ChordPro/Output/HTML.pm +++ b/lib/App/Music/ChordPro/Output/HTML.pm @@ -129,6 +129,13 @@ sub generate_song { nhtml($e->{text}) . '' ); next; } + if ( $e->{type} eq "set" && $e->{name} eq "label" ) { + push( @s, + '
' . nhtml($e->{value}) . '
' + ); + next; + } + } push( @s, '' ); push( @s, "" ) if $tidy; diff --git a/lib/App/Music/ChordPro/Output/LaTeX.pm b/lib/App/Music/ChordPro/Output/LaTeX.pm new file mode 100644 index 00000000..434e8237 --- /dev/null +++ b/lib/App/Music/ChordPro/Output/LaTeX.pm @@ -0,0 +1,341 @@ +#!/usr/bin/perl + +package App::Music::ChordPro::Output::LaTeX; +# Author: Johannes Rumpf / 2022 +# relevant Latex packages - still using the template module would make it possible +# to create any form of textual output. +# delivered example will work with songs-package - any other package needed to be +# evaluated / tested. But should work +# http://songs.sourceforge.net/songsdoc/songs.html +# https://www.ctan.org/pkg/songs +# https://www.ctan.org/pkg/guitar +# https://www.ctan.org/pkg/songbook +# https://www.ctan.org/pkg/gchords + +use strict; +use warnings; +use App::Music::ChordPro::Output::Common; +use Template; +use LaTeX::Encode; + +our $CHORDPRO_LIBRARY; + +my $single_space = 0; # suppress chords line when empty +my $lyrics_only = 0; # suppress all chords lines +my %line_routines = (); +my $gtemplate; +my $gcfg; + +my $newpage_tag = "[% newpage_tag %]" ; +my $emptyline_tag = "[% emptyline_tag %]"; +my $columnbreak_tag = "[% columnbreak_tag %]"; +my $beginchorus_tag = "[% beginchorus_tag %]"; +my $endchorus_tag = "[% endchorus_tag %]"; +my $beginverse_tag = "[% beginverse_tag %]"; +my $endverse_tag = "[% endverse_tag %]"; +my $beginabc_tag = "[% beginabc_tag %]"; +my $endabc_tag = "[% endabc_tag %]"; +my $beginlilypond_tag = "[% beginlilypond_tag %]"; +my $endlilypond_tag = "[% endlilypond_tag %]"; +my $begingrid_tag = "[% begingrid_tag %]"; +my $endgrid_tag = "[% endgrid_tag %]"; +my $begintab_tag = "[% begintab_tag %]"; +my $endtab_tag = "[% endtab_tag %]"; +my $gchordstart_tag = "[% gchordstart_tag %]"; +my $gchordend_tag = "[% gchordend_tag %]"; +my $chorded_line = "[% chorded_line %]"; +my $unchorded_line = "[% unchorded_line %]"; +my $start_spaces_songline = "[% start_spaces_songline %]"; +my $eol = "[% eol %]"; + +sub generate_songbook { + my ( $self, $sb ) = @_; + my @songs; + $gcfg = $::config->{latex}; + $gtemplate = Template->new({ + INCLUDE_PATH => [@{$gcfg->{template_include_path}}, ::rsc_or_file("res/templates/"), $CHORDPRO_LIBRARY], + INTERPOLATE => 1, + }) || die "$Template::ERROR\n"; + + foreach my $song ( @{$sb->{songs}} ) { + push( @songs, generate_song($song) ); + } + my $songbook = ''; + my %vars = (); + $vars{songs} = [@songs] ; + $gtemplate->process($gcfg->{templates}->{songbook}, \%vars, $::options->{output} ) + || die $gtemplate->error(); + # i like it more to handle output through template module - but its possible to result it as array. + # return split(/\n/, $songbook); + $::options->{output} = '-'; + return []; +} + +# some not implemented feature is requested. will be removed. +sub line_default { + my ( $lineobject, $ref_lineobjects ) = @_; + return ""; +} +$line_routines{line_default} = \&line_default; + +sub get_firstphrase{ + my ( $elts ) = @_; # reference to array + my $line = ""; + foreach my $elt (@{ $elts }) { + if($elt->{type} eq 'songline'){ + foreach my $phrase (@{$elt->{phrases}}){ + $line .= $phrase; + } + return my_latex_encode($line); + } + } +} + +sub line_songline { + my ( $lineobject ) = @_; + my $index = 0; + my $line = ""; + my $chord = ""; + my $has_chord = 0; + foreach my $phrase (@{$lineobject->{phrases}}){ + if(defined $lineobject->{chords}){ + if (@{$lineobject->{chords}}[$index] ne '' ){ + $chord = $gchordstart_tag.@{$lineobject->{chords}}[$index] .$gchordend_tag; #songbook format \\[chord] + $has_chord = 1; + }} + $line .= $chord . latex_encode($phrase); + $index += 1; + $chord = ""; + } + + my $empty = $line; + my $textline = $line; + my $nbsp = $start_spaces_songline; #unicode for nbsp sign # start_spaces_songline + if($empty =~ /^\s+/){ # starts with spaces + $empty =~ s/^(\s+).*$/$1/; # not the elegant solution - but working - replace all spaces in the beginning of a line + my $replaces = $empty; #with a nbsp symbol as the intend tend to be intentional + $replaces =~ s/\s+/$nbsp/g; + $textline =~ s/$empty/$replaces/; + } + $line = $textline; + if ($has_chord) { $line = $chorded_line . $line; } else { $line = $unchorded_line . $line; } + return $line.$eol; +} +$line_routines{line_songline} = \&line_songline; + +sub line_newpage { + my ( $lineobject ) = @_; + return $newpage_tag; +} +$line_routines{line_newpage} = \&line_newpage; + +sub line_empty { + my ( $lineobject ) = @_; + return $emptyline_tag; +} +$line_routines{line_empty} = \&line_empty; + +sub line_comment { + my ( $lineobject ) = @_; # Template for comment? + my $vars = { + comment => latex_encode($lineobject->{text}) + }; + my $comment = ''; + $gtemplate->process($gcfg->{templates}->{comment}, $vars, \$comment) || die $gtemplate->error(); + return $comment ; +} +$line_routines{line_comment} = \&line_comment; + +sub line_comment_italic { + my ( $lineobject ) = @_; # Template for comment? + my $vars = { + comment => "\\textit{". latex_encode($lineobject->{text}) ."}" + }; + my $comment = ''; + $gtemplate->process($gcfg->{templates}->{comment}, $vars, \$comment) || die $gtemplate->error(); + return $comment; +} +$line_routines{line_comment_italic} = \&line_comment_italic; + +sub line_image { + my ( $lineobject ) = @_; + my $image = ''; + $gtemplate->process($gcfg->{templates}->{image}, $lineobject, \$image)|| die $gtemplate->error(); + return $image; +} +$line_routines{line_image} = \&line_image; + +sub line_colb { + my ( $lineobject ) = @_; # Template for comment? + return $columnbreak_tag; +} +$line_routines{line_colb} = \&line_colb; + +sub line_chorus { + my ( $lineobject ) = @_; # + return $beginchorus_tag ."\n". + elt_handler($lineobject->{body}) . + $endchorus_tag . "\n"; +} +$line_routines{line_chorus} = \&line_chorus; + +sub line_verse { + my ( $lineobject ) = @_; # + return $beginverse_tag ."\n". + elt_handler($lineobject->{body}) + .$endverse_tag ."\n"; +} +$line_routines{line_verse} = \&line_verse; + +sub line_set { # potential comments in fe. Chorus or verse or .... complicated handling - potential contextsensitiv. + my ( $lineobject ) = @_; + return ''; +} +$line_routines{line_set} = \&line_set; + +sub line_tabline { + my ( $lineobject ) = @_; + return $lineobject->{text}.$eol; +} +$line_routines{line_tabline} = \&line_tabline; + +sub line_tab { + my ( $lineobject ) = @_; + return $begintab_tag."\n". + elt_handler($lineobject->{body}) . + $endtab_tag ."\n"; +} +$line_routines{line_tab} = \&line_tab; + +sub line_grid { + my ( $lineobject ) = @_; + return $begingrid_tag."\n". + elt_handler($lineobject->{body}) + .$endgrid_tag ."\n"; +} +$line_routines{line_grid} = \&line_grid; + +sub line_gridline { + my ( $lineobject ) = @_; + my $line = ''; + if(defined $lineobject->{margin}){ + $line .= $lineobject->{margin}->{text} . "\t"; + } + else { + $line .= "\t\t"; + } + foreach my $token (@{ $lineobject->{tokens} }){ + if ($token->{class} eq 'chord'){ + $line .= $token->{chord}; + } + else { + $line .= $token->{symbol}; + } + } + if(defined $lineobject->{comment}){ + $line .= $lineobject->{comment}->{text}; + } + return $line. $eol; +} +$line_routines{line_gridline} = \&line_gridline; + +sub elt_handler { + my ( $elts ) = @_; # reference to array + my $cref; #command reference to subroutine + + my $lines = ""; + foreach my $elt (@{ $elts }) { + # Gang of Four-Style - sort of command pattern + my $sub_type = "line_".$elt->{type}; # build command "line_" + # if (exists &{$sub_type}) { #check if sub is implemented / maybe hash is -would be- faster... + if (defined $line_routines{$sub_type}) { + $cref = $line_routines{$sub_type}; #\&$sub_type; # due to use strict - we need to get an reference to the command + $lines .= &$cref($elt); # call line with actual line-object + } + else { + $lines .= line_default($elt); # default = empty line + + } + } + return $lines; +} + +sub my_latex_encode{ + my ( $val ) = @_; + if ((ref($val) eq 'SCALAR') or ( ref($val) eq '' )) { return latex_encode($val); } + if (ref($val) eq 'ARRAY'){ + my @array_return; + foreach my $array_val (@{$val}){ + push(@array_return, my_latex_encode($array_val)); + } + return \@array_return; + } + if (ref($val) eq 'HASH'){ + my %hash_return = (); + foreach my $hash_key (keys( % {$val } )){ + $hash_return{$hash_key} = my_latex_encode( $val->{$hash_key} ); + } + return \%hash_return; + } +} + +sub generate_song { + my ( $s ) = @_; + my %gtemplatatevar = (); + + if ( defined $s->{meta} ) { + $gtemplatatevar{meta} = my_latex_encode($s->{meta}); + } + $gtemplatatevar{meta}->{index} = get_firstphrase($s->{body}); # needs unstructured data - .. redesign? + + # asume songline a verse when no context is applied. # check https://github.com/ChordPro/chordpro/pull/211 + # Songbook needs to have a verse otherwise the chords-makro is not in the right context + foreach my $item ( @{ $s->{body} } ) { + if ( $item->{type} eq "songline" && $item->{context} eq '' ){ + $item->{context} = 'verse'; + }} # end of pull -- + $s->structurize; # removes empty lines + + + for ( $s->{title} // "Untitled" ) { + $gtemplatatevar{title} = my_latex_encode($s->{title}); + } + if ( defined $s->{subtitle} ) { + $gtemplatatevar{subtitle} = my_latex_encode($s->{subtitle}); + } + + + if ( defined $s->{chords}->{chords} ) { + my @chords; + foreach my $mchord (@{$s->{chords}->{chords}}){ + # replace -1 with 'x' - alternative '-' + my $frets = join("", map { if($_ eq '-1'){ $_ = 'X'; } +"$_"} @{$s->{chordsinfo}->{$mchord}->{frets}}); + my %chorddef = ( + "chord" => $mchord, + "frets" => $frets, + "base" => $s->{chordsinfo}->{$mchord}->{base}, + "fingers" => $s->{chordsinfo}->{$mchord}->{fingers}); + push(@chords, \%chorddef); + } + $gtemplatatevar{chords} = \@chords; + } + + $gtemplatatevar{songlines} = elt_handler($s->{body}); + + # my $song = ''; + # $gtemplate->process($gcfg->{template_song}, \%gtemplatatevar, \$song) || die $gtemplate->error(); + + return \%gtemplatatevar; + #$song; +} + +1; + +#not implemented line-types +# sub line_rechorus { +# my ( $lineobject ) = @_; +# } + +# sub line_control { +# my ( $lineobject ) = @_; +# } diff --git a/lib/App/Music/ChordPro/Output/Markdown.pm b/lib/App/Music/ChordPro/Output/Markdown.pm new file mode 100644 index 00000000..9d471150 --- /dev/null +++ b/lib/App/Music/ChordPro/Output/Markdown.pm @@ -0,0 +1,396 @@ +#!/usr/bin/perl +package main; + +our $options; +our $config; + +package App::Music::ChordPro::Output::Markdown; +# Author: Johannes Rumpf / 2022 + +use strict; +use warnings; +use App::Music::ChordPro::Output::Common; +use Text::Layout::Markdown; + +my $single_space = 0; # suppress chords line when empty +my $lyrics_only = 0; # suppress all chords lines +my $chords_under = 0; # chords under lyrics +my $text_layout = Text::Layout::Markdown->new; # Text::Layout::Text->new; +my %line_routines = (); +my $tidy; +my $rechorus; # not implemented @todo +my $act_song; +my $cp = "\t"; # Chord-Prefix // Verbatim / Code line in Markdown + +sub upd_config { + $lyrics_only = $config->{settings}->{'lyrics-only'}; + $chords_under = $config->{settings}->{'chords-under'}; + $rechorus = $config->{text}->{chorus}->{recall}; +} + +sub generate_songbook { + my ( $self, $sb ) = @_; + my @book; + # push(@book, "[TOC]"); # maybe https://metacpan.org/release/IMAGO/Markdown-TOC-0.01 to create a TOC? + + foreach my $song ( @{$sb->{songs}} ) { + if ( @book ) { + push(@book, "") if $options->{'backend-option'}->{tidy}; + } + push(@book, @{generate_song($song)}); + push(@book, "--------------- \n"); #Horizontal line between each song + } + + push( @book, ""); + + # remove all double empty lines + my @new; + my $count = 0; + foreach (@book){ + if ($_ =~ /.{1,}/ ){ + push(@new, $_); + $count = 0 + } else { + push(@new, $_) if $count == 0; + $count++; + } + } + \@new; +} + +sub generate_song { + my ( $s ) = @_; + $act_song = $s; + $tidy = $options->{'backend-option'}->{tidy}; + $single_space = $options->{'single-space'}; + + upd_config(); + + # asume songline a verse when no context is applied. # check https://github.com/ChordPro/chordpro/pull/211 + foreach my $item ( @{ $s->{body} } ) { + if ( $item->{type} eq "songline" && $item->{context} eq '' ){ + $item->{context} = 'verse'; + }} # end of pull -- + + $s->structurize; + my @s; + push(@s, "# " . $s->{title}) if defined $s->{title}; + if ( defined $s->{subtitle} ) { + push(@s, map { +"## $_" } @{$s->{subtitle}}); + } + + if ( $lyrics_only eq 0 ){ + my $all_chords = ""; + # https://chordgenerator.net/D.png?p=xx0212&s=2 # reuse of other projects (https://github.com/einaregilsson/ChordImageGenerator)? + # generate png-out of this project? // fingers also possible - but not set in basics. + foreach my $mchord (@{$s->{chords}->{chords}}){ + # replace -1 with 'x' - alternative '-' + my $frets = join("", map { if($_ eq '-1'){ $_ = 'x'; } +"$_"} @{$s->{chordsinfo}->{$mchord}->{frets}}); + $all_chords .= "![$mchord](https://chordgenerator.net/$mchord.png?p=$frets&s=2) "; + + } + push(@s, $all_chords); + push(@s, ""); + } + push(@s, elt_handler($s->{body})); + return \@s; +} + +sub line_default { + my ( $lineobject, $ref_lineobjects ) = @_; + return ""; +} +$line_routines{line_default} = \&line_default; + +sub chord { + my ( $c ) = @_; + return "" unless length($c); + my $ci = $act_song->{chordsinfo}->{$c}; + return "<<$c>>" unless defined $ci; + $text_layout->set_markup($ci->show); + my $t = $text_layout->render; + return $ci->is_annotation ? "*$t" : $t; +} + +sub md_textline{ + my ( $songline ) = @_; + my $empty = $songline; + my $textline = $songline; + my $nbsp = "\x{00A0}"; #unicode for nbsp sign + if($empty =~ /^\s+/){ # starts with spaces + $empty =~ s/^(\s+).*$/$1/; # not the elegant solution - but working - replace all spaces in the beginning of a line + my $replaces = $empty; #with a nbsp symbol as the intend tend to be intentional + $replaces =~ s/\s/$nbsp/g; + $textline =~ s/$empty/$replaces/; + } + $textline = $textline." "; # append two spaces to force linebreak in Markdown + return $textline; +} + +sub line_songline { + my ( $elt ) = @_; + my $t_line = ""; + my @phrases = map { $text_layout->set_markup($_); $text_layout->render } + @{ $elt->{phrases} }; + + if ( $lyrics_only or + $single_space && ! ( $elt->{chords} && join( "", @{ $elt->{chords} } ) =~ /\S/ ) + ) { + $t_line = join( "", @phrases ); + return md_textline($cp.$t_line); + } + + unless ( $elt->{chords} ) { # i guess we have a line with no chords now... + return ($cp. md_textline( join( " ", @phrases )) ); + } + + if ( my $f = $::config->{settings}->{'inline-chords'} ) { + $f = '[%s]' unless $f =~ /^[^%]*\%s[^%]*$/; + $f .= '%s'; + foreach ( 0..$#{$elt->{chords}} ) { + $t_line .= sprintf( $f, + chord( $elt->{chords}->[$_] ), + $phrases[$_] ); + } + return ( md_textline($cp.$t_line) ); + } + + my $c_line = ""; + foreach ( 0..$#{$elt->{chords}} ) { + $c_line .= chord( $elt->{chords}->[$_] ) . " "; + $t_line .= $phrases[$_]; + my $d = length($c_line) - length($t_line); + $t_line .= "-" x $d if $d > 0; + $c_line .= " " x -$d if $d < 0; + } # this looks like setting the chords above the words. + + s/\s+$// for ( $t_line, $c_line ); + + # main problem in markdown - a fixed position is only available in "Code escapes" so weather to set + # a tab or a double backticks (``) - i tend to the tab - so all lines with tabs are "together" + if ($c_line ne ""){ # Block-lines are not replacing initial spaces - as the are "code" + $t_line = $cp.$t_line." "; + $c_line = $cp.$c_line." "; + } + else{ + $t_line = md_textline($cp.$t_line); + } + return $chords_under + ? ( $t_line, $c_line ) + : ( $c_line, $t_line ); +} +$line_routines{line_songline} = \&line_songline; + +sub line_newpage { + return "--------------- \n"; +} +$line_routines{line_newpage} = \&line_newpage; + +sub line_empty { + return "$cp"; +} +$line_routines{line_empty} = \&line_empty; + +sub line_comment { + my ( $elt ) = @_; # Template for comment? + my @s; + my $text = $elt->{text}; + if ( $elt->{chords} ) { + $text = ""; + for ( 0..$#{ $elt->{chords} } ) { + $text .= "[" . $elt->{chords}->[$_] . "]" + if $elt->{chords}->[$_] ne ""; + $text .= $elt->{phrases}->[$_]; + }} + if ($elt->{type} =~ /italic$/) { + $text = "*" . $text . "* "; + } + push(@s, "> $text "); + return @s; +} +$line_routines{line_comment} = \&line_comment; + +sub line_comment_italic { + my ( $lineobject ) = @_; # Template for comment? + return "> *". $lineobject->{text} ."*";; +} +$line_routines{line_comment_italic} = \&line_comment_italic; + + +sub line_image { + my ( $elt ) = @_; + return "![](".$elt->{uri}.")"; +} +$line_routines{line_image} = \&line_image; + +sub line_colb { + return "\n\n\n"; +} +$line_routines{line_colb} = \&line_colb; + +sub body_has_chords{ + my ( $elts ) = @_; # reference to array + my $has_chord = 0; # default false has no chords + foreach my $elt (@{ $elts }) { + if ($elt->{type} eq 'songline'){ + if ((defined $elt->{chords}) && (scalar @{$elt->{chords}} > 0 )){ + $has_chord = 1; + return $has_chord; + }} + } + return $has_chord; +} +sub line_chorus { + my ( $lineobject ) = @_; # + my @s; + $cp = (body_has_chords($lineobject->{body})) ? "\t" : ""; # Verbatim on Verse/Chorus because Chords are present + push(@s, "**Chorus**"); + push(@s, ""); + push(@s, elt_handler($lineobject->{body})); + # push(@s, "\x{00A0} "); # nbsp + push(@s, "--------------- \n"); + return @s; +} +$line_routines{line_chorus} = \&line_chorus; + +sub line_verse { + my ( $lineobject ) = @_; # + my @s; + $cp = (body_has_chords($lineobject->{body})) ? "\t" : ""; # Verbatim on Verse/Chorus because Chords are present + push(@s, elt_handler($lineobject->{body})); + push(@s, ""); + # push(@s, "\x{00A0} "); # nbsp + return @s; +} +$line_routines{line_verse} = \&line_verse; + +sub line_set { # potential comments in fe. Chorus or verse or .... complicated handling - potential contextsensitiv. + my ( $elt ) = @_; + if ( $elt->{name} eq "lyrics-only" ) { + $lyrics_only = $elt->{value} + unless $lyrics_only > 1; + } + # Arbitrary config values. + elsif ( $elt->{name} =~ /^(text\..+)/ ) { + my @k = split( /[.]/, $1 ); + my $cc = {}; + my $c = \$cc; + foreach ( @k ) { + $c = \($$c->{$_}); + } + $$c = $elt->{value}; + $config->augment($cc); + upd_config(); + } + return ""; +} +$line_routines{line_set} = \&line_set; + +sub line_tabline { + my ( $lineobject ) = @_; + return "\t".$lineobject->{text}; +} +$line_routines{line_tabline} = \&line_tabline; + +sub line_tab { + my ( $lineobject ) = @_; + my @s; + push(@s, "**Tabulatur** "); #@todo + push(@s, ""); + push(@s, map { "\t".$_ } elt_handler($lineobject->{body}) ); #maybe this need to go for code markup as well´? + return @s; +} +$line_routines{line_tab} = \&line_tab; + +sub line_grid { + my ( $lineobject ) = @_; + my @s; + push(@s, "**Grid** "); + push(@s, ""); + push(@s, elt_handler($lineobject->{body})); + # push(@s, "\x{00A0} "); + push(@s, ""); + return @s; +} +$line_routines{line_grid} = \&line_grid; + +sub line_gridline { + my ( $elt ) = @_; + my @a = @{ $elt->{tokens} }; + @a = map { $_->{class} eq 'chord' + ? $_->{chord} + : $_->{symbol} } @a; + return "\t".join("", @a); +} +$line_routines{line_gridline} = \&line_gridline; + +sub elt_handler { + my ( $elts ) = @_; # reference to array + my $cref; #command reference to subroutine + my $init_context = 1; + my $ctx = ""; + + my @lines; + my $last_type=''; + foreach my $elt (@{ $elts }) { + if (($elt->{type} eq 'verse') && ($last_type =~ /comment/)){ + push(@lines, ""); + } + # Gang of Four-Style - sort of command pattern + my $sub_type = "line_".$elt->{type}; # build command "line_" + if (defined $line_routines{$sub_type}) { + $cref = $line_routines{$sub_type}; #\&$sub_type; # due to use strict - we need to get an reference to the command + push(@lines, &$cref($elt)); # call line with actual line-object + } + else { + push(@lines, line_default($elt)); # default = empty line + } + $last_type = $elt->{type}; + } + return @lines; +} + +################# + +# package Text::Layout::Text; + +# use parent 'Text::Layout'; + +# # Eliminate warning when HTML backend is loaded together with Text backend. +# no warnings 'redefine'; + +# sub new { +# my ( $pkg, @data ) = @_; +# my $self = $pkg->SUPER::new; +# $self; +# } + +# sub render { +# my ( $self ) = @_; +# my $res = ""; +# foreach my $fragment ( @{ $self->{_content} } ) { +# next unless length($fragment->{text}); +# $res .= $fragment->{text}; +# } +# $res; +# } + + +1; +# @todo +# sub line_rechorus { +# my ( $lineobject ) = @_; + # if ( $rechorus->{quote} ) { + # unshift( @elts, @{ $elt->{chorus} } ); + # } + # elsif ( $rechorus->{type} && $rechorus->{tag} ) { + # push( @s, "{".$rechorus->{type}.": ".$rechorus->{tag}."}" ); + # } + # else { + # push( @s, "{chorus}" ); + # } +# } + +# sub line_control { +# my ( $lineobject ) = @_; +# } diff --git a/lib/App/Music/ChordPro/Output/PDF.pm b/lib/App/Music/ChordPro/Output/PDF.pm index 5d0f7500..30fe1325 100644 --- a/lib/App/Music/ChordPro/Output/PDF.pm +++ b/lib/App/Music/ChordPro/Output/PDF.pm @@ -15,6 +15,7 @@ use App::Packager; use File::Temp (); use Storable qw(dclone); use List::Util qw(any); +use feature 'state'; use App::Music::ChordPro::Output::Common qw( roman prep_outlines fmt_subst demarkup ); @@ -383,7 +384,7 @@ sub generate_song { $fonts->{$item}->{file} = $_; } elsif ( is_corefont($_) ) { - $fonts->{$item}->{name} = $_; + $fonts->{$item}->{name} = is_corefont($_); } else { $fonts->{$item}->{description} = $_; @@ -558,13 +559,34 @@ sub generate_song { $s->{meta}->{page} = [ $s->{page} = $opts->{roman} ? roman($thispage) : $thispage ]; - # Determine page class. + # Determine page class and background. my $class = 2; # default + my $bgpdf = $ps->{formats}->{default}->{background}; if ( $thispage == 1 ) { $class = 0; # very first page + $bgpdf = $ps->{formats}->{first}->{background} + || $ps->{formats}->{title}->{background} + || $bgpdf; } elsif ( $thispage == $startpage ) { $class = 1; # first of a song + $bgpdf = $ps->{formats}->{title}->{background} + || $bgpdf; + } + if ( $bgpdf ) { + my ( $fn, $pg ) = ( $bgpdf, 1 ); + if ( $bgpdf =~ /^(.+):(\d+)$/ ) { + ( $bgpdf, $pg ) = ( $1, $2 ); + } + $fn = ::rsc_or_file($bgpdf); + if ( -s -r $fn ) { + $pg++ if $ps->{"even-odd-pages"} && !$rightpage; + $pr->importpage( $fn, $pg ); + } + else { + warn( "PDF: Missing or empty background document: ", + $bgpdf, "\n" ); + } } $x = $ps->{__leftmargin}; @@ -604,8 +626,12 @@ sub generate_song { $show //= $dctl->{show}; if ( $chords ) { for ( @$chords ) { - my $i = $s->{chordsinfo}->{$_}; - push( @chords, $i ) unless $i->is_nc; + if ( my $i = $s->{chordsinfo}->{$_} ) { + push( @chords, $i ) unless $i->is_nc; + } + else { + warn("PDF: Missing chord info for \"$_\"\n"); + } } } return unless @chords; @@ -737,7 +763,7 @@ sub generate_song { $newpage->(); # Embed source and config for debugging; - $pr->embed($source->{file}) if $options->{debug}; + $pr->embed($source->{file}) if $source->{file} && $options->{debug}; my @elts = @{$sb}; my $elt; # current element @@ -1003,7 +1029,16 @@ sub generate_song { eval "require $pkg" || die($@); my $hd = $pkg->can($elt->{handler}) // die("PDF: Missing delegate handler ${pkg}::$elt->{handler}\n"); - my $res = $hd->( $s, $pr, $elt ); + my $pw; # available width + if ( $ps->{columns} > 1 ) { + $pw = $ps->{columnoffsets}->[1] + - $ps->{columnoffsets}->[0] + - $ps->{columnspace}; + } + else { + $pw = $ps->{__rightmargin} - $ps->{_leftmargin}; + } + my $res = $hd->( $s, $pw, $elt ); next unless $res; # assume errors have been given unshift( @elts, @$res ); next; @@ -1040,6 +1075,63 @@ sub generate_song { next; } + if ( $elt->{type} eq "svg" ) { + # We turn SVG into one (or more) XForm objects. + + require App::Music::ChordPro::Output::PDF::SVG; + my $p = App::Music::ChordPro::Output::PDF::SVG->new + ( $ps, debug => $config->{debug}->{images} > 1 ); + my $o = $p->process_file( $elt->{uri} ); + warn("PDF: SVG objects: ", 0+@$o, "\n") + if $config->{debug}->{images} || !@$o; + if ( ! @$o ) { + warn("Error in SVG embedding\n"); + next; + } + + my @res; + for my $xo ( @$o ) { + state $imgcnt = 0; + my $assetid = sprintf("XFOasset%03d", $imgcnt++); + $assets->{$assetid} = { type => "xform", data => $xo }; + + push( @res, + { type => "xform", + width => $xo->{width}, + height => $xo->{height}, + id => $assetid, + opts => { center => $elt->{opts}->{center}, + scale => $elt->{opts}->{scale} || 1 } }, + ); + warn("Created asset $assetid (xform, ", + $xo->{width}, "x", $xo->{height}, ")", + " scale=", $elt->{opts}->{scale} || 1, + " center=", $elt->{opts}->{center}//0, + "\n") + if $config->{debug}->{images}; + } + + unshift( @elts, @res ); + next; + } + + if ( $elt->{type} eq "xform" ) { + my $h = $elt->{height}; + my $w = $elt->{width}; + my $scale = $elt->{opts}->{scale}; + my $vsp = $h * $scale; + $checkspace->($vsp); + $ps->{pr}->show_vpos( $y, 1 ) if $config->{debug}->{spacing}; + + my $xo = $assets->{ $elt->{id} }; + $pr->{pdfgfx}->object( $xo->{data}->{xo}, $x, $y-$vsp, $scale ); + + $y -= $vsp; + $pr->show_vpos( $y, 1 ) if $config->{debug}->{spacing}; + + next; + } + if ( $elt->{type} eq "rechorus" ) { my $t = $ps->{chorus}->{recall}; if ( $t->{type} !~ /^comment(?:_italic|_box)?$/ ) { @@ -1132,7 +1224,7 @@ sub generate_song { elsif ( is_corefont( $elt->{value} ) ) { delete $ps->{fonts}->{$f}->{description}; delete $ps->{fonts}->{$f}->{file}; - $ps->{fonts}->{$f}->{name} = $elt->{value}; + $ps->{fonts}->{$f}->{name} = is_corefont( $elt->{value} ); } else { delete $ps->{fonts}->{$f}->{file}; @@ -1571,7 +1663,7 @@ sub songline { # Collect chords to be printed in the side column. my $info = $opts{song}->{chordsinfo}->{$chord}; croak("Missing info for chord $chord") unless $info; - $chord = $info->chord_display; + $chord = $info->chord_display( has_musicsyms($fchord) ); push(@chords, $chord); } else { @@ -1580,7 +1672,7 @@ sub songline { if ( $chord ne '' ) { my $info = $opts{song}->{chordsinfo}->{$chord}; Carp::croak("Missing info for chord $chord") unless $info; - $chord = $info->chord_display; + $chord = $info->chord_display( has_musicsyms($font) ); my $dp = $chord . " "; if ( $info->is_annotation ) { $font = $fonts->{annotation}; @@ -1662,6 +1754,18 @@ sub songline { return; } +sub has_musicsyms { + my ( $font ) = @_; + my $sf = 0; + $sf |= 0x01 + if $font->{has_sharp} //= + $font->{fd}->{font}->glyphByUni(ord("♯")) ne ".notdef"; + $sf |= 0x02 + if $font->{has_flat} //= + $font->{fd}->{font}->glyphByUni(ord("♭")) ne ".notdef"; + return $sf; +} + sub is_bar { exists( $_[0]->{class} ) && $_[0]->{class} eq "bar"; } @@ -1791,7 +1895,7 @@ sub gridline { if ( exists $token->{chord} ) { my $t = $token->{chord}; my $i = $opts{song}->{chordsinfo}->{$t}; - $t = $i->chord_display if $i; + $t = $i->chord_display( has_musicsyms($fchord) ) if $i; $pr->text( $t, $x, $y, $fchord ) unless $token eq "."; $x += $cellwidth; @@ -2246,7 +2350,7 @@ sub configurator { else { die("Config error: \"$_\" is not a built-in font\n") unless is_corefont($_); - $fonts->{$type}->{name} = $_; + $fonts->{$type}->{name} = is_corefont($_); } } for ( $options->{"$type-size"} ) { @@ -2411,8 +2515,14 @@ sub wrap { if ( @rchords ) { # Does the chord fit? - my $font = $pr->{ps}->{fonts}->{chord}; - $pr->setfont($font); + my $c = $chord; + if ( $chord =~ /^\*(.+)/ ) { + $c = $1; + $pr->setfont( $pr->{ps}->{fonts}->{annotation} ); + } + else { + $pr->setfont( $pr->{ps}->{fonts}->{chord} ); + } my $w = $pr->strwidth($chord); if ( $w > $m - $x ) { # Nope. Move to overflow. @@ -2470,30 +2580,40 @@ sub wrapsimple { $pr->wrap( $text, $pr->{ps}->{__rightmargin} - $x ); } -my %corefonts = map { $_ => 1 } - ( "times-roman", - "times-bold", - "times-italic", - "times-bolditalic", - "helvetica", - "helvetica-bold", - "helvetica-oblique", - "helvetica-boldoblique", - "courier", - "courier-bold", - "courier-oblique", - "courier-boldoblique", - "zapfdingbats", - "georgia", - "georgia,bold", - "georgia,italic", - "georgia,bolditalic", - "verdana", - "verdana,bold", - "verdana,italic", - "verdana,bolditalic", - "webdings", - "wingdings" ); +my %corefonts = + ( + ( map { lc($_) => $_ } + "Times-Roman", + "Times-Bold", + "Times-Italic", + "Times-BoldItalic", + "Helvetica", + "Helvetica-Bold", + "Helvetica-Oblique", + "Helvetica-BoldOblique", + "Courier", + "Courier-Bold", + "Courier-Oblique", + "Courier-BoldOblique", + "ZapfDingbats", + "Georgia", + "Georgia,Bold", + "Georgia,Italic", + "Georgia,BoldItalic", + "Verdana", + "Verdana,Bold", + "Verdana,Italic", + "Verdana,BoldItalic", + "Webdings", + "Wingdings" ), + # For convenience. + "georgia-bold" => "Georgia,Bold", + "georgia-italic" => "Georgia,Italic", + "georgia-bolditalic" => "Georgia,BoldItalic", + "verdana-bold" => "Verdana,Bold", + "verdana-italic" => "Verdana,Italic", + "verdana-bolditalic" => "Verdana,BoldItalic", +); sub is_corefont { $corefonts{lc $_[0]}; diff --git a/lib/App/Music/ChordPro/Output/PDF/StringDiagrams.pm b/lib/App/Music/ChordPro/Output/PDF/StringDiagrams.pm index 99a847db..f31c2192 100644 --- a/lib/App/Music/ChordPro/Output/PDF/StringDiagrams.pm +++ b/lib/App/Music/ChordPro/Output/PDF/StringDiagrams.pm @@ -27,7 +27,7 @@ sub new { # The vertical space the diagram requires. sub vsp0 { my ( $self, $elt, $ps ) = @_; - $ps->{fonts}->{diagram}->{size} * 1.2 + $ps->{fonts}->{diagram}->{size} * $ps->{spacing}->{diagramchords} + 0.40 * $ps->{diagrams}->{width} + $ps->{diagrams}->{vcells} * $ps->{diagrams}->{height}; } @@ -87,19 +87,20 @@ sub draw { # Draw font name. my $font = $ps->{fonts}->{diagram}; $pr->setfont($font); - my $name = $info->chord_display; + my $name = $info->chord_display + ( App::Music::ChordPro::Output::PDF::has_musicsyms($font) ); $name .= "*" unless $info->{origin} ne "user" || $::config->{diagrams}->{show} eq "user"; $pr->text( $name, $x + ($w - $pr->strwidth($name))/2, $y - font_bl($font) ); - $y -= $font->{size} * 1.2 + $dot/2 + $lw; + $y -= $font->{size} * $ps->{spacing}->{diagramchords} + $dot/2 + $lw; if ( $info->{base} + $info->{baselabeloffset} > 1 ) { # my $i = @Roman[$info->{base}] . " "; my $i = sprintf("%d ", $info->{base} + $info->{baselabeloffset}); $pr->setfont( $ps->{fonts}->{diagram_base}, $gh ); $pr->text( $i, $x-$pr->strwidth($i), $y-($info->{baselabeloffset}*$gh)-0.85*$gh, - $ps->{fonts}->{diagram_base}, 1.2*$gh ); + $ps->{fonts}->{diagram_base}, $ps->{spacing}->{diagramchords}*$gh ); $pr->setfont($font); } diff --git a/lib/App/Music/ChordPro/Output/PDF/Writer.pm b/lib/App/Music/ChordPro/Output/PDF/Writer.pm index 166d8269..361cdd0f 100644 --- a/lib/App/Music/ChordPro/Output/PDF/Writer.pm +++ b/lib/App/Music/ChordPro/Output/PDF/Writer.pm @@ -370,6 +370,7 @@ sub newpage { $self->{pdfpage} = $self->{pdf}->page($page||0); $self->{pdfpage}->mediabox( $ps->{papersize}->[0], $ps->{papersize}->[1] ); + $self->{pdfgfx} = $self->{pdfpage}->gfx; $self->{pdftext} = $self->{pdfpage}->text; unless ($ps->{theme}->{background} =~ /^white|none|#ffffff$/i ) { @@ -392,6 +393,17 @@ sub openpage { $self->{pdftext} = $self->{pdfpage}->text; } +sub importpage { + my ( $self, $fn, $pg ) = @_; + my $bg = $self->{pdfapi}->open($fn); + return unless $bg; # should have been checked + $pg = $bg->pages if $pg > $bg->pages; + $self->{pdf}->import_page( $bg, $pg, $self->{pdfpage} ); + # Make sure the contents get on top of it. + $self->{pdfgfx} = $self->{pdfpage}->gfx; + $self->{pdftext} = $self->{pdfpage}->text; +} + sub pagelabel { my ( $self, $page, $style, $prefix ) = @_; $style //= 'arabic'; @@ -526,6 +538,10 @@ sub init_fonts { } } + # Make sure we have this one. + $fc->register_font( "ChordProSymbols.ttf", "chordprosymbols", "", {} ); + + # Process the fontconfig. foreach my $ff ( keys( %{ $ps->{fontconfig} } ) ) { my @fam = split( /\s*,\s*/, $ff ); foreach my $s ( keys( %{ $ps->{fontconfig}->{$ff} } ) ) { @@ -605,11 +621,12 @@ sub init_corefont { my $ps = $self->{ps}; my $font = $ps->{fonts}->{$ff}; + my $cf = App::Music::ChordPro::Output::PDF::is_corefont($font->{name}); die("Config error: \"$font->{name}\" is not a built-in font\n") - unless App::Music::ChordPro::Output::PDF::is_corefont($font->{name}); + unless $cf; my $fc = Text::Layout::FontConfig->new; eval { - $font->{fd} = $fc->from_filename($font->{name}); + $font->{fd} = $fc->from_filename($cf); $font->{fd}->get_font($self->{layout}); # force load $font->{_ff} = $ff; }; diff --git a/lib/App/Music/ChordPro/Song.pm b/lib/App/Music/ChordPro/Song.pm index 0ecfd734..2b220256 100644 --- a/lib/App/Music/ChordPro/Song.pm +++ b/lib/App/Music/ChordPro/Song.pm @@ -238,32 +238,7 @@ sub parse_song { $self->{chordsinfo} = {}; # Preprocessor. - my $prep; - if ( $config->{parser} ) { - foreach my $linetype ( keys %{ $config->{parser}->{preprocess} } ) { - my @targets; - my $code; - foreach ( @{ $config->{parser}->{preprocess}->{$linetype} } ) { - if ( $_->{pattern} ) { - push( @targets, $_->{pattern} ); - # Subsequent targets override. - $code->{$_->{pattern}} = $_->{replace}; - } - else { - push( @targets, quotemeta($_->{target}) ); - # Subsequent targets override. - $code->{quotemeta($_->{target})} = quotemeta($_->{replace}); - } - } - if ( @targets ) { - my $t = "sub { for (\$_[0]) {\n"; - $t .= "s\0" . $_ . "\0" . $code->{$_} . "\0g;\n" for @targets; - $t .= "}}"; - $prep->{$linetype} = eval $t; - die( "CODE : $t\n$@" ) if $@; - } - } - } + my $prep = make_preprocessor( $config->{parser}->{preprocess} ); # Pre-fill meta data, if any. TODO? ALREADY DONE? if ( $options->{meta} ) { @@ -534,19 +509,23 @@ sub parse_song { $diagrams = "none"; } - if ( $diagrams =~ /^(user|all)$/ ) { - my %h; - @used_chords = map { $h{$_}++ ? () : $_ } @used_chords; + { my %h; @used_chords = map { $h{$_}++ ? () : $_ } @used_chords; } - if ( $diagrams eq "user" && $self->{define} && @{$self->{define}} ) { - @used_chords = - map { $_->{name} } @{$self->{define}}; - } + if ( $diagrams eq "user" && $self->{define} && @{$self->{define}} ) { + @used_chords = + map { $_->{name} } @{$self->{define}}; + } - if ( $config->{diagrams}->{sorted} ) { - @used_chords = - sort App::Music::ChordPro::Chords::chordcompare @used_chords; - } + if ( $config->{diagrams}->{sorted} ) { + @used_chords = + sort App::Music::ChordPro::Chords::chordcompare @used_chords; + } + + # For headings, footers, table of contents, ... + $self->{meta}->{chords} //= [ @used_chords ]; + $self->{meta}->{numchords} = [ scalar(@{$self->{meta}->{chords}}) ]; + + if ( $diagrams =~ /^(user|all)$/ ) { $self->{chords} = { type => "diagrams", origin => "song", @@ -608,7 +587,7 @@ sub chord { ( { name => $c, text => $c } ) ); return $c; } - ( my $n = $name ) =~ s/\((.+)\)$/$1/; + ( my $n = $name ) =~ s/^\((.+)\)$/$1/; if ( ! $info->{origin} && $config->{diagrams}->{auto} ) { $info = App::Music::ChordPro::Chords::add_unknown_chord($name); @@ -669,7 +648,7 @@ sub decompose { } # Recall memorized chords. - elsif ( $memchords ) { + elsif ( $memchords && $in_context ) { if ( $memcrdinx == 0 && @$memchords == 0 ) { do_warn("No chords memorized for $in_context"); push( @chords, $chord ); @@ -828,9 +807,15 @@ sub parse_directive { if ( $dir =~ /^(.*)-(.+)$/ ) { $dir = $abbrevs{$1} // $1; my $sel = $2; - unless ( $sel eq lc($config->{instrument}->{type}) - or - $sel eq lc($config->{user}->{name}) ) { + my $negate = $sel =~ s/\!$//; + $sel = ( $sel eq lc($config->{instrument}->{type}) ) + || + ( $sel eq lc($config->{user}->{name}) + || + ( $self->{meta}->{lc $sel} && is_true($self->{meta}->{lc $sel}->[0]) ) + ); + $sel = !$sel if $negate; + unless ( $sel ) { if ( $dir =~ /^start_of_/ ) { return { name => $dir, arg => $arg, omit => 2 }; } @@ -1049,7 +1034,7 @@ sub directive { do_warn( "Missing image source\n" ); return; } - $self->add( type => "image", + $self->add( type => $uri =~ /\.svg$/ ? "svg" : "image", uri => $uri, opts => \%opts ); return 1; @@ -1657,7 +1642,7 @@ sub parse_chord { if ( $info ) { # Look it up now, the name may change by transcode. - if ( my $i = App::Music::ChordPro::Chords::_known_chord($info->name) ) { + if ( my $i = App::Music::ChordPro::Chords::_known_chord($info) ) { warn( "Parsing chord: \"$chord\" found ", $i->name, " for ", $info->name, " in song/config chords\n" ) if $debug > 1; $info = $i->new({ %$i, name => $info->name }) ; @@ -1676,7 +1661,7 @@ sub parse_chord { $info->name, " (", $info->{system}, ")", "\n" ) if $debug > 1; - if ( my $i = App::Music::ChordPro::Chords::_known_chord($info->name) ) { + if ( my $i = App::Music::ChordPro::Chords::_known_chord($info) ) { warn( "Parsing chord: \"$chord\" found \"", $info->name, "\" in song/config chords\n" ) if $debug > 1; $unk = 0; @@ -1712,10 +1697,14 @@ sub parse_chord { if ( $info ) { warn( "Parsing chord: \"$chord\" okay: \"", - $info->name, "\"", + $info->name, "\" \"", + $info->chord_display, "\"", $unk ? " but unknown" : "", "\n" ) if $debug > 1; - $info->{parens} = $parens if $parens; + if ( $parens ) { + $self->store_chord( $info->clone ); + $info->{parens} = $parens; + } $chord = $self->store_chord($info); return wantarray ? ( $chord, $info ) : $chord; } @@ -1743,6 +1732,9 @@ sub structurize { $context = $def_context; next; } + if ( $item->{type} eq "songline" && $item->{context} eq '' ){ # A songline should have a context - non means verse + $item->{context} = 'verse'; + } if ( $context ne $item->{context} ) { push( @body, { type => $context = $item->{context}, body => [] } ); } diff --git a/lib/App/Music/ChordPro/Testing.pm b/lib/App/Music/ChordPro/Testing.pm index 7b57a2bd..a055f148 100644 --- a/lib/App/Music/ChordPro/Testing.pm +++ b/lib/App/Music/ChordPro/Testing.pm @@ -48,7 +48,7 @@ sub is_deeply { } } } - for ( qw( instrument user key_from key_actual ) ) { + for ( qw( instrument user key_from key_actual chords numchords ) ) { delete $got->{meta}->{$_} unless exists $expect->{meta}->{$_}; } } diff --git a/lib/App/Music/ChordPro/Utils.pm b/lib/App/Music/ChordPro/Utils.pm index 176f8629..752fd492 100644 --- a/lib/App/Music/ChordPro/Utils.pm +++ b/lib/App/Music/ChordPro/Utils.pm @@ -69,7 +69,7 @@ sub findexe { my ( $prog ) = @_; my @path; if ( MSWIN ) { - $prog .= ".exe"; + $prog .= ".exe" unless $prog =~ /\.\w+$/; @path = split( ';', $ENV{PATH} ); unshift( @path, '.' ); } @@ -101,6 +101,41 @@ sub sys { push( @EXPORT, 'sys' ); +################ (Pre)Processing ################ + +sub make_preprocessor { + my ( $prp ) = @_; + return unless $prp; + + my $prep; + foreach my $linetype ( keys %{ $prp } ) { + my @targets; + my $code; + foreach ( @{ $prp->{$linetype} } ) { + if ( $_->{pattern} ) { + push( @targets, $_->{pattern} ); + # Subsequent targets override. + $code->{$_->{pattern}} = $_->{replace}; + } + else { + push( @targets, quotemeta($_->{target}) ); + # Subsequent targets override. + $code->{quotemeta($_->{target})} = quotemeta($_->{replace}); + } + } + if ( @targets ) { + my $t = "sub { for (\$_[0]) {\n"; + $t .= "s\0" . $_ . "\0" . $code->{$_} . "\0g;\n" for @targets; + $t .= "}}"; + $prep->{$linetype} = eval $t; + die( "CODE : $t\n$@" ) if $@; + } + } + $prep; +} + +push( @EXPORT, 'make_preprocessor' ); + ################ Utilities ################ # Split (pseudo) command line into key/value pairs. @@ -129,4 +164,15 @@ sub parse_kv { push( @EXPORT, 'parse_kv' ); +# Map true/false etc to true / false. + +sub is_true { + my ( $arg ) = @_; + return if !defined($arg); + return if $arg =~ /^(false|null|0+)$/i; + return !!$arg; +} + +push( @EXPORT, 'is_true' ); + 1; diff --git a/lib/App/Music/ChordPro/Version.pm b/lib/App/Music/ChordPro/Version.pm index f5c767ae..ee9f81c5 100644 --- a/lib/App/Music/ChordPro/Version.pm +++ b/lib/App/Music/ChordPro/Version.pm @@ -1,4 +1,4 @@ # This file is generated. Do not edit! package App::Music::ChordPro::Version; -our $VERSION = "5.987"; +our $VERSION = "5.988"; print "$VERSION\n" unless caller; diff --git a/lib/App/Music/ChordPro/Wx/PreferencesDialog.pm b/lib/App/Music/ChordPro/Wx/PreferencesDialog.pm index 3c952ef0..3dd2d9a4 100644 --- a/lib/App/Music/ChordPro/Wx/PreferencesDialog.pm +++ b/lib/App/Music/ChordPro/Wx/PreferencesDialog.pm @@ -244,8 +244,8 @@ sub store_prefs { $parent->{prefs_xpose_acc} = $self->{ch_acc}->GetSelection; $n = $parent->{prefs_xpose_to} - $parent->{prefs_xpose_from}; $n += 12 if $n < 0; - $n -= 12 if $parent->{prefs_xpose_acc} == 1; - $n += 12 if $parent->{prefs_xpose_acc} == 2; + $n += 12 if $parent->{prefs_xpose_acc} == 1; # sharps + $n -= 12 if $parent->{prefs_xpose_acc} == 2; # flats $parent->{prefs_xpose} = $n; # Transcode. diff --git a/lib/App/Music/ChordPro/res/config/chordpro.json b/lib/App/Music/ChordPro/res/config/chordpro.json index fca9f36d..d22c80ec 100644 --- a/lib/App/Music/ChordPro/res/config/chordpro.json +++ b/lib/App/Music/ChordPro/res/config/chordpro.json @@ -55,6 +55,9 @@ "chords-canonical" : false, // If false, chorus labels are used as tags. "choruslabels" : true, + // Substitute Unicode sharp/flats in chord names. + // Will fallback to ChordProSymbols the font doesn't have the glyphs. + "truesf" : false, }, // Metadata. @@ -215,6 +218,7 @@ "handler" : "abc2image", "config" : "default", // or "none", or "myformat.fmt" "preamble" : [], + "preprocess" : { "abc" : [], "svg" : [] }, }, "ly" : { "type" : "image", @@ -279,6 +283,7 @@ "title" : 1.2, "lyrics" : 1.2, "chords" : 1.2, + "diagramchords" : 1.2, "grid" : 1.2, "tab" : 1.0, "toc" : 1.4, @@ -389,7 +394,8 @@ "pagealign-songs" : 1, // Formats. - // Pages have two title elements and one footer element. + // Pages have two title elements and one footer element. They also + // can have a page of an existing PDF file as underlay (background). // Topmost is "title". It uses the "title" font as defined further below. // Second is "subtitle". It uses the "subtitle" font. // The "footer" uses the "footer" font. @@ -405,10 +411,12 @@ // title/subtitle fields. Don't try to add an artist page element. "formats" : { - // Titles/Footers. + // Titles/Footers. - // Titles/footers have 3 parts, which are printed left, + // Titles/footers have 3 parts, which are printed left, // centered and right. + // For odd/even printing, the 1st background page is used + // for left pages and the next page (if it exists) for right pages. // For even/odd printing, the order is reversed. // By default, a page has: @@ -418,6 +426,8 @@ "subtitle" : [ "", "", "" ], // Footer is title -- page number. "footer" : [ "%{title}", "", "%{page}" ], + // Background page. + "background" : "", }, // The first page of a song has: "title" : { @@ -426,12 +436,16 @@ "subtitle" : [ "", "%{subtitle}", "" ], // Footer with page number. "footer" : [ "", "", "%{page}" ], + // Background page. + "background" : "", }, // The very first output page is slightly different: "first" : { // It has title and subtitle, like normal 'first' pages. // But no footer. "footer" : [ "", "", "" ], + // Background page. + "background" : "", }, }, @@ -627,6 +641,16 @@ }, }, + // Settings for LaTeX backend. + "latex" : { + "template_include_path" : [ ], + "templates" : { + "songbook" : "songbook.tt", + "comment" : "comment.tt", + "image" : "image.tt" + } + }, + // Settings for Text backend. "text" : { // Style of chorus. diff --git a/lib/App/Music/ChordPro/res/config/config.schema b/lib/App/Music/ChordPro/res/config/config.schema index 60fb763a..0beffcfc 100644 --- a/lib/App/Music/ChordPro/res/config/config.schema +++ b/lib/App/Music/ChordPro/res/config/config.schema @@ -33,6 +33,9 @@ "module": { "type": "string", "default": "ABC" + }, + "preprocess": { + "type": "object" } }, "required" : [ "type", "handler", "module" ], @@ -139,6 +142,13 @@ "default": false }, + "choruslabels": { + "description": "", + "title": "If false, chorus labels are used as tags.", + "type": "boolean", + "default": true + }, + "columns": { "description": "Number of columns.", "type": "integer", @@ -231,6 +241,12 @@ "minimum" : -12, "maximum" : 12, "format" : "number" + }, + + "truesf": { + "description": "Substitute Unicode sharp/flats in chord names.", + "type": "boolean", + "default": false } } }, @@ -695,6 +711,41 @@ } }, + "latex": { + "title": "LaTeX backend", + "description": "", + "type": "object", + "additionalProperties": false, + "properties": { + "template_include_path": { + "description": "Include paths for templates.", + "additionalProperties": false, + "type" : "array" + }, + "templates" : { + "description": "Templates for LaTeX generation.", + "additionalProperties": false, + "properties": { + "comment": { + "description": "Helper template to render comments.", + "type": "string", + "default" : "comment.tt" + }, + "image": { + "description": "Helper template to render images.", + "type": "string", + "default" : "image.tt" + }, + "songbook": { + "description": "Master template to render the songbook.", + "type": "string", + "default" : "songbook.tt" + } + } + } + } + }, + "parser": { "title": "Preprocessing", "description": "Preprocessing the input.", @@ -1103,6 +1154,10 @@ "description": "Default properties for all pages.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "allOf": [ { "$ref": "#/definitions/tptspec" }, @@ -1124,6 +1179,10 @@ "description": "Properties for per-song title pages.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "allOf": [ { "$ref": "#/definitions/tptspec" }, @@ -1145,6 +1204,10 @@ "description": "Properties of the very first page.", "additionalProperties": false, "properties": { + "background": { + "description": "Background page.", + "type": "string" + }, "title": { "description": "Defaults to default.", "$ref": "#/definitions/tptspec" diff --git a/lib/App/Music/ChordPro/res/config/guitar_latex.json b/lib/App/Music/ChordPro/res/config/guitar_latex.json new file mode 100644 index 00000000..02563e4e --- /dev/null +++ b/lib/App/Music/ChordPro/res/config/guitar_latex.json @@ -0,0 +1,9 @@ +{ + "latex" : { // please check for LaTeX Songbook/Guitar packages and have a look into config-examples + "templates" : { + "songbook" : "guitar_songbook.tt", + "comment" : "guitar_comment.tt", + "image" : "guitar_image.tt" + } + } +} diff --git a/lib/App/Music/ChordPro/res/config/songbook_latex.json b/lib/App/Music/ChordPro/res/config/songbook_latex.json new file mode 100644 index 00000000..13bba7f4 --- /dev/null +++ b/lib/App/Music/ChordPro/res/config/songbook_latex.json @@ -0,0 +1,9 @@ +{ + "latex" : { // please check for LaTeX Songbook/Guitar packages and have a look into config-examples + "templates" : { + "songbook" : "songbook.tt", + "comment" : "comment.tt", + "image" : "image.tt" + } + } +} diff --git a/lib/App/Music/ChordPro/res/examples/bgdemo.pdf b/lib/App/Music/ChordPro/res/examples/bgdemo.pdf new file mode 100644 index 00000000..59691dab Binary files /dev/null and b/lib/App/Music/ChordPro/res/examples/bgdemo.pdf differ diff --git a/lib/App/Music/ChordPro/res/fonts/ChordProSymbols.ttf b/lib/App/Music/ChordPro/res/fonts/ChordProSymbols.ttf index 7172b096..e00358e4 100644 Binary files a/lib/App/Music/ChordPro/res/fonts/ChordProSymbols.ttf and b/lib/App/Music/ChordPro/res/fonts/ChordProSymbols.ttf differ diff --git a/lib/App/Music/ChordPro/res/fonts/README.md b/lib/App/Music/ChordPro/res/fonts/README.md index c8d1d4f4..7b997937 100644 --- a/lib/App/Music/ChordPro/res/fonts/README.md +++ b/lib/App/Music/ChordPro/res/fonts/README.md @@ -1,10 +1,15 @@ # Special Symbols for ChordPro The tool `uc.py` creates a font `ChordProSymbols.ttf` out of -`NotoSansSymbols-Regular.ttf`. +`Cadman.ttf` and `NotoSansSymbols-Regular.ttf`. Most important are the black circles with digits 0 .. 9 and letters A .. Z. These are used for fingering in string diagrams. For non-fingered strings, the glyph at `/` is used. This is just a copy of the circled 1 but will be drawn on a foreground spot, making it a fully filled circle. + +V000.200 + +Redesign font to em 2048. +Use Cadman font for musical symbols. diff --git a/lib/App/Music/ChordPro/res/fonts/uc.py b/lib/App/Music/ChordPro/res/fonts/uc.py index 8f8be0d3..0810ae79 100644 --- a/lib/App/Music/ChordPro/res/fonts/uc.py +++ b/lib/App/Music/ChordPro/res/fonts/uc.py @@ -31,18 +31,20 @@ def gcopy(name, f, tf = 0 ): dst.familyname = "MusicalSymbolsForChordPro" dst.fullname = "Musical Symbols For ChordPro" dst.copyright = "Open Font License" -dst.version = "000.100" -dst.em = 1000; +dst.version = "000.200" +dst.em = 2048; # Next slot for glyphs. ind = 33; # Copy glyphs from Symbols font. -src = fontforge.open("NotoSansSymbols-Regular.ttf") +src = fontforge.open("Cadman.ttf") gcopy( "Flat", 0x266d ) gcopy( "Natural", 0x266e ) gcopy( "Sharp", 0x266f ) +src = fontforge.open("NotoSansSymbols-Regular.ttf") +src.em = dst.em ind = 47 # We're missing a fullly filled circle for unfingered strings. # Use one of the others and draw it on a black circle. diff --git a/lib/App/Music/ChordPro/res/pod/Config.pod b/lib/App/Music/ChordPro/res/pod/Config.pod index eed8d38f..8f1a6a41 100644 --- a/lib/App/Music/ChordPro/res/pod/Config.pod +++ b/lib/App/Music/ChordPro/res/pod/Config.pod @@ -82,6 +82,9 @@ extensive details and examples. "chords-canonical" : false, // If false, chorus labels are used as tags. "choruslabels" : true, + // Substitute Unicode sharp/flats in chord names. + // Will fallback to ChordProSymbols the font doesn't have the glyphs. + "truesf" : false, }, // Metadata. @@ -242,6 +245,7 @@ extensive details and examples. "handler" : "abc2image", "config" : "default", // or "none", or "myformat.fmt" "preamble" : [], + "preprocess" : { "abc" : [], "svg" : [] }, }, "ly" : { "type" : "image", @@ -306,6 +310,7 @@ extensive details and examples. "title" : 1.2, "lyrics" : 1.2, "chords" : 1.2, + "diagramchords" : 1.2, "grid" : 1.2, "tab" : 1.0, "toc" : 1.4, @@ -416,7 +421,8 @@ extensive details and examples. "pagealign-songs" : 1, // Formats. - // Pages have two title elements and one footer element. + // Pages have two title elements and one footer element. They also + // can have a page of an existing PDF file as underlay (background). // Topmost is "title". It uses the "title" font as defined further below. // Second is "subtitle". It uses the "subtitle" font. // The "footer" uses the "footer" font. @@ -432,10 +438,12 @@ extensive details and examples. // title/subtitle fields. Don't try to add an artist page element. "formats" : { - // Titles/Footers. + // Titles/Footers. - // Titles/footers have 3 parts, which are printed left, + // Titles/footers have 3 parts, which are printed left, // centered and right. + // For odd/even printing, the 1st background page is used + // for left pages and the next page (if it exists) for right pages. // For even/odd printing, the order is reversed. // By default, a page has: @@ -445,6 +453,8 @@ extensive details and examples. "subtitle" : [ "", "", "" ], // Footer is title -- page number. "footer" : [ "%{title}", "", "%{page}" ], + // Background page. + "background" : "", }, // The first page of a song has: "title" : { @@ -453,12 +463,16 @@ extensive details and examples. "subtitle" : [ "", "%{subtitle}", "" ], // Footer with page number. "footer" : [ "", "", "%{page}" ], + // Background page. + "background" : "", }, // The very first output page is slightly different: "first" : { // It has title and subtitle, like normal 'first' pages. // But no footer. "footer" : [ "", "", "" ], + // Background page. + "background" : "", }, }, @@ -654,6 +668,16 @@ extensive details and examples. }, }, + // Settings for LaTeX backend. + "latex" : { + "template_include_path" : [ ], + "templates" : { + "songbook" : "songbook.tt", + "comment" : "comment.tt", + "image" : "image.tt" + } + }, + // Settings for Text backend. "text" : { // Style of chorus. diff --git a/lib/App/Music/ChordPro/res/templates/comment.tt b/lib/App/Music/ChordPro/res/templates/comment.tt new file mode 100644 index 00000000..6a4c662d --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/comment.tt @@ -0,0 +1,7 @@ +\begin{tcolorbox}[colback=gray!30,%gray background + colframe=black,% black frame colour + width=\textwidth-2cm,% Use 5cm total width, + arc=3mm, auto outer arc, + size=small] + \chordsoff [% comment %] +\end{tcolorbox} diff --git a/lib/App/Music/ChordPro/res/templates/guitar_comment.tt b/lib/App/Music/ChordPro/res/templates/guitar_comment.tt new file mode 100644 index 00000000..4658978d --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/guitar_comment.tt @@ -0,0 +1,7 @@ +\begin{tcolorbox}[colback=gray!30, + colframe=black, + width=\textwidth-2cm, + arc=3mm, auto outer arc, + size=small] + [% comment %] +\end{tcolorbox} diff --git a/lib/App/Music/ChordPro/res/templates/guitar_image.tt b/lib/App/Music/ChordPro/res/templates/guitar_image.tt new file mode 100644 index 00000000..1c4214e0 --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/guitar_image.tt @@ -0,0 +1,3 @@ +\begin{center} [% # image parameters are at opts. f.e. opts.width, opts.scale, opts.height ... %] + \includegraphics[width=[% IF opts.scale %][% opts.scale %][% ELSE %]0.75[% END %]\textwidth,keepaspectratio]{[% uri %]} \\ +\end{center} \ No newline at end of file diff --git a/lib/App/Music/ChordPro/res/templates/guitar_songbook.tt b/lib/App/Music/ChordPro/res/templates/guitar_songbook.tt new file mode 100644 index 00000000..358ffd6d --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/guitar_songbook.tt @@ -0,0 +1,62 @@ +[% # these variables are obligatory to set + newpage_tag = '\newpage' _ "\n" # _ is concatenate + emptyline_tag = '\newline'_"\n" + columnbreak_tag = "\\par\n" + beginchorus_tag = "\\begin{tcolorbox}[boxrule=0pt,frame hidden,sharp corners,enhanced,borderline west={1pt}{0pt}{black},colback=white]" + endchorus_tag = "\\end{tcolorbox}" + beginverse_tag = "" + endverse_tag = '\newline' + beginabc_tag = "\\begin{abc}" + endabc_tag = "\\end{abc}" + beginlilypond_tag = "\\begin{lilypond}" + endlilypond_tag = "\\end{lilypond}" + begingrid_tag = "\\begin{singlespace*}\n\\begin{verbatim}" + endgrid_tag = "\\end{verbatim}\n\\end{singlespace*}" + begintab_tag = "\\begin{singlespace*}\n\\begin{verbatim}" + endtab_tag = "\\end{verbatim}\n\\end{singlespace*}" + gchordstart_tag = "\\guitarChord{" + gchordend_tag = "}" + chorded_line = "" # \\guitarAccord when using \begin{guitar} + unchorded_line = "" # \\guitarNoChord + start_spaces_songline = "\\hspace{0,5cm}" #if a songline starts with spaces it will be replaced by start_spaces_songline + eol = '\\\\' _ "\n" #sometimes this need to be to \\ or \newline +%] +% https://ftp.rrze.uni-erlangen.de/ctan/macros/latex/contrib/guitar/guitar.pdf +\documentclass[a4paper]{article} + +\usepackage{guitar} +\usepackage[most]{tcolorbox} +\usepackage[TS1,T1]{fontenc} +\usepackage[bookmarks]{hyperref} +\usepackage{gchords} +\begin{document} +\smallchords + +\title{my Songbook} +\author{The Author} +\date{\today} +\maketitle + +\section{Songbook-chapter} + +[% FOREACH song IN songs %] +\newpage +\subsection{[% song.title %]} +[% IF song.chords.0 %]\begin{tcolorbox}[colback=white,colframe=black,width=0.75\textwidth,arc=3mm, auto outer arc] + \chords{ +[% FOREACH chord IN song.chords %]\chord{t}{[% FOREACH fret IN chord.frets.split(''); +IF fret != 'X'; + IF fret != '0'; + 'p'; fret; END; +ELSE; 'x'; +END; +IF not loop.last; ','; END; +END; -%]}{[% chord.chord %]} [% elements = loop.count % 5 %][% IF ((elements == 0) and (not loop.last)) %]\newline[% END %][% END %] + }\end{tcolorbox}[% END %] + +[% FOREACH st IN song.subtitle %] [% st %] \\[% END %] +[% IF song.meta.composer.0 %] Composer [% song.meta.composer.0 %] \\[% END %][% IF song.meta.lyricist.0 %]lyricist [% song.meta.lyricist.0 %] \\[% END %][% IF song.meta.copyright.0 %](c) [% song.meta.copyright.0 %] \\[% END %] +[% song.songlines | eval #eval is obligatory to eval the variables set above %] +[% END %] + +\end{document} \ No newline at end of file diff --git a/lib/App/Music/ChordPro/res/templates/image.tt b/lib/App/Music/ChordPro/res/templates/image.tt new file mode 100644 index 00000000..4eee2c95 --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/image.tt @@ -0,0 +1,4 @@ + \beginverse* + \centering [% # image parameters are at opts. f.e. opts.width, opts.scale, opts.height ... %] + \includegraphics[width=[% IF opts.scale %][% opts.scale %][% ELSE %]0.75[% END %]\textwidth,keepaspectratio]{[% uri %]} + \endverse diff --git a/lib/App/Music/ChordPro/res/templates/songbook.tt b/lib/App/Music/ChordPro/res/templates/songbook.tt new file mode 100644 index 00000000..d01f020e --- /dev/null +++ b/lib/App/Music/ChordPro/res/templates/songbook.tt @@ -0,0 +1,98 @@ +[% # these variables are obligatory to set + newpage_tag = "\\newpage\n" + emptyline_tag = "\\newpage\n" + columnbreak_tag = "\\columnbreak\n" + beginchorus_tag = "\\beginchorus" + endchorus_tag = "\\endchorus" + beginverse_tag = "\\beginverse" + endverse_tag = "\\endverse" + beginabc_tag = "\\begin{abc}" + endabc_tag = "\\end{abc}" + beginlilypond_tag = "\\begin{lilypond}" + endlilypond_tag = "\\end{lilypond}" + begingrid_tag = "\\begin{singlespace*}\n\\begin{verbatim}" + endgrid_tag = "\\end{verbatim}\n\\end{singlespace*}" + begintab_tag = "\\begin{singlespace*}\n\\begin{verbatim}" + endtab_tag = "\\end{verbatim}\n\\end{singlespace*}" + gchordstart_tag = "\\[" + gchordend_tag = "]" + chorded_line = "\\chordson " # Songs-package require this to know when to switch + unchorded_line = "\\chordsoff " # chordlines off - it will be printed ahead of a song line + start_spaces_songline = "\\hspace{0,5cm}" #if a songline starts with spaces it will be replaced by start_spaces_songline + eol = "\n" #sometimes this need to be to \\ or \newline +%] +% http://songs.sourceforge.net/songsdoc/songs.html +\documentclass[a5paper]{book} +\usepackage{graphicx} +\usepackage[TS1,T1]{fontenc} +\usepackage[most]{tcolorbox} +\usepackage[bookmarks]{hyperref} +\usepackage{fancyhdr} +\usepackage[chorded]{songs} + +\pagestyle{fancy} % Eigener Seitenstil +\fancyhf{} % Alle Kopf- und Fußzeilenfelder bereinigen +%\fancyhead[L]{Titel} % Kopfzeile links +%\fancyhead[C]{} % Zentrierte Kopfzeile +%\fancyhead[R]{Name} % Kopfzeile rechts +%\renewcommand{\headrulewidth}{0.4pt} % Obere Trennlinie +\fancyfoot[C]{\thepage} % Seitennummer +\renewcommand{\footrulewidth}{0pt} % Untere Trennlinie löschen +\renewcommand{\headrulewidth}{0pt} %obere weg. + +% \includeonlysongs{2} +%\setlength{\oddsidemargin}{1cm} +%\setlength{\evensidemargin}{1cm} +\setlength{\textwidth}{10cm} +\setlength{\topmargin}{-1cm} +\setlength{\topskip}{0in} +\setlength{\headheight}{0in} +\setlength{\headsep}{0in} +\setlength{\textheight}{18cm} +\settowidth{\versenumwidth}{1.\ } +\renewcommand{\printchord}[1]{\rmfamily\bf#1} +%\renewcommand{\songmark}{\markboth{\thesongnum}{\thesongnum}} +\noversenumbers +\setlength{\sbarheight}{0pt} % no horizontal rule above / below songs + +\setcounter{songnum}{1} +\songpos{2} %how hard should a song match on one side 1-3 + +\newindex{titleidx}{cbtitle} +\newauthorindex{authidx}{cbauth} +\newscripindex{scripidx}{cbscrip} + +%\songcolumns{1} +\songcolumns{0} %disable column management of Song package +%\baselineadj=-3pt plus 1pt minus 0pt % distance reduce + + +\begin{document} +\frontmatter +\showindex{Index}{titleidx} + +\mainmatter +\begin{songs}{titleidx,authidx,scripidx} +[% FOREACH song IN songs %] +\newpage +\beginsong{[% song.title %][% FOREACH st IN song.subtitle %] \\ [% st %][% END %]} +[ + by={[% song.meta.composer.0 %]}, + sr={[% song.meta.lyricist.0 %]}, + cr={[% song.meta.copyright.0 %]}, + index={[% song.meta.index %]}] +[% IF song.meta.capo.0 %]\capo{[% song.meta.capo.0 %]}[% END %] +[% IF song.chords.0 %]\begin{tcolorbox}[colback=white,colframe=black,width=0.75\textwidth,arc=3mm, auto outer arc] +[% FOREACH chord IN song.chords %]\gtab{[% chord.chord %]}{[% chord.frets %]}[% elements = loop.count % 5 %][% IF ((elements == 0) and (not loop.last)) %]\newline[% END %][% END %]\end{tcolorbox}[% END %] + +[% song.songlines | eval %] +\endsong + +[% END %] +\end{songs} + +\pagenumbering{Alph} +%\showindex{Index of Authors and Composers}{authidx} +%\showindex{Index of Scripture}{scripidx} + +\end{document} \ No newline at end of file diff --git a/pp/docker/Dockerfile b/pp/docker/Dockerfile new file mode 100644 index 00000000..af2396cd --- /dev/null +++ b/pp/docker/Dockerfile @@ -0,0 +1,56 @@ +# syntax=docker/dockerfile:1 +FROM debian:stable-slim AS base + +# Environment +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=en_US.UTF-8 \ + LC_ALL=C.UTF-8 \ + LANGUAGE=en_US.UTF-8 + +# Install perl and runtime packages. +# We have a libpdf-api2-perl but that's not recent enough. +RUN apt-get update \ + && apt-get install -y \ + build-essential \ + perl \ + libwx-perl \ + mupdf \ + cpanminus + +# Install perl modules and clean up. +RUN apt-get install --no-install-recommends -y \ + libxml-perl \ + libfile-homedir-perl \ + libfont-ttf-perl \ + libimage-info-perl \ + abcm2ps \ + imagemagick \ + librsvg2-bin \ + libimage-magick-perl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Perl modules from CPAN. +RUN cpanm \ + PDF::API2 \ + File::LoadLines \ + String::Interpolate::Named \ + HarfBuzz::Shaper \ + Text::Layout + +FROM base + +# Augmented version of App::Packeger. +RUN cpanm App::Packager +COPY CPAN/App/Packager.pm Packager.pm +RUN perl -MConfig -e 'print "mv Packager.pm $Config{sitelib}/App/Packager.pm\n"' | bash -x + +# Add ChordPro +RUN cpanm chordpro + +# Cleanup. +RUN rm -fr .cpanminus + +# Setup for run-time +ENV DOCKER_PACKAGED=1.00 +RUN chordpro --about diff --git a/script/make_bgdemo.pl b/script/make_bgdemo.pl new file mode 100644 index 00000000..729ebe15 --- /dev/null +++ b/script/make_bgdemo.pl @@ -0,0 +1,36 @@ +#!/usr/bin/perl + +use v5.20; + +use PDF::API2; + +my $n = shift || 1; + +my ( $width, $height ) = ( 595, 842 ); # 16/10 tablet + +my $pdf = PDF::API2->new; +$pdf->mediabox( 0, 0, $width, $height ); +my $font = $pdf->corefont("Helvetica"); + +my @tags = qw( FIRST First Other ); + +foreach my $t ( @tags ) { + + my $page = $pdf->page; + my $text = $page->text; + $text->font( $font, 140 ); + $text->fillcolor( "lightblue" ); + my $w = $text->advancewidth($t); + $text->translate( 40, 500 ); + $text->text($t); + + $page = $pdf->page; + $text = $page->text; + $text->font( $font, 140 ); + $text->fillcolor( "lightblue" ); + my $w = $text->advancewidth($t); + $text->translate( $width-$w-40, 500 ); + $text->text($t); +} + +$pdf->saveas("bgdemo.pdf"); diff --git a/t/190_outlines.t b/t/190_outlines.t index 5848fe46..5bdf1ee8 100644 --- a/t/190_outlines.t +++ b/t/190_outlines.t @@ -67,6 +67,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -94,6 +99,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -121,6 +131,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -148,6 +163,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -175,6 +195,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'A', 'Am', @@ -203,6 +228,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], @@ -230,6 +260,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], diff --git a/t/191_outlines.t b/t/191_outlines.t index 7dabd951..69619558 100644 --- a/t/191_outlines.t +++ b/t/191_outlines.t @@ -65,6 +65,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], @@ -92,6 +97,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], @@ -119,6 +129,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -146,6 +161,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -173,6 +193,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'A', 'Am', @@ -201,6 +226,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -228,6 +258,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], diff --git a/t/192_outlines.t b/t/192_outlines.t index fe31abf3..a73688ff 100644 --- a/t/192_outlines.t +++ b/t/192_outlines.t @@ -67,6 +67,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], @@ -94,6 +99,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'D', ], @@ -121,6 +131,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -148,6 +163,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -175,6 +195,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'A', 'Am', @@ -203,6 +228,11 @@ my $xp = [ artist => [ 'December', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], @@ -230,6 +260,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'C', ], diff --git a/t/193_outlines.t b/t/193_outlines.t index ac1b2cde..59ee1b55 100644 --- a/t/193_outlines.t +++ b/t/193_outlines.t @@ -64,6 +64,11 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], + numchords => [ + 0, + ], key => [ 'A', ], @@ -87,12 +92,17 @@ my $xp = [ artist => [ 'September', ], + chords => [ + ], key => [ 'Am', ], key_actual => [ 'Am', ], + numchords => [ + 0, + ], songindex => 1, sorttitle => [ 'Fietspomp, De', @@ -111,12 +121,17 @@ my $xp = [ 'September', 'December', ], + chords => [ + ], key => [ 'C', ], key_actual => [ 'C', ], + numchords => [ + 0, + ], songindex => 3, sorttitle => [ 'Fietsenhok, Het', @@ -137,12 +152,17 @@ my $xp = [ 'September', 'December', ], + chords => [ + ], key => [ 'D', ], key_actual => [ 'D', ], + numchords => [ + 0, + ], songindex => 2, sorttitle => [ 'Vierentwintig Fietsen', diff --git a/t/73_md.t b/t/73_md.t new file mode 100644 index 00000000..f157cf9f --- /dev/null +++ b/t/73_md.t @@ -0,0 +1,39 @@ +#! perl + +use strict; +use warnings; +use utf8; + +use App::Music::ChordPro::Testing; + +my $test = 0; + +BAIL_OUT("Missing md test data") unless -d "md"; + +opendir( my $dh, "md" ) || BAIL_OUT("Cannot open md test data"); +my @files = grep { /^.+\.cho$/ } readdir($dh); +close($dh); +diag("Testing ", scalar(@files), " cho files"); + +our $options; + +foreach my $file ( sort @files ) { + $test++; + my $decoda = $file =~ /^decoda/i; + $file = "md/$file"; + #diag("Testing: $file"); + ( my $out = $file ) =~ s/\.cho/.tmp/; + ( my $ref = $file ) =~ s/\.cho/.md/; + @ARGV = ( "--no-default-configs", + "--generate", "Markdown", + "--output", $out, + $file ); + ::run(); + my $ok = !differ( $out, $ref ); + ok( $ok, $file ); + unlink($out) if $ok; +} + +ok( $test++ == @files, "Tested @{[0+@files]} files" ); + +done_testing($test); diff --git a/t/74_latex.t b/t/74_latex.t new file mode 100644 index 00000000..ee528833 --- /dev/null +++ b/t/74_latex.t @@ -0,0 +1,51 @@ +#! perl + +use strict; +use warnings; +use utf8; + +use App::Music::ChordPro::Testing; + +BAIL_OUT("Missing md test data") unless -d "md"; + +opendir( my $dh, "latex" ) || BAIL_OUT("Cannot open md test data"); +my @files = grep { /^.+\.cho$/ } readdir($dh); +close($dh); +my $numtests = @files; +my $test = 0; +plan tests => 1+$numtests; + +SKIP: { + + unless ( eval { require Template } ) { + diag( 'Skipped all tests -- missing Template module' ); + skip( 'Missing Template module', 1+$numtests ); + } + + unless ( eval { require LaTeX::Encode } ) { + diag( 'Skipped all tests -- missing LaTeX::Encode module' ); + skip( 'Missing LaTeX::Encode module', 1+$numtests ); + } + + diag("Testing ", scalar(@files), " cho files"); + + our $options; + + foreach my $file ( sort @files ) { + $test++; + $file = "latex/$file"; + #diag("Testing: $file"); + ( my $out = $file ) =~ s/\.cho/.tmp/; + ( my $ref = $file ) =~ s/\.cho/.tex/; + @ARGV = ( "--no-default-configs", "--config", "./latex/t_config.json", + "--generate", "LaTeX", + "--output", $out, + $file ); + ::run(); + my $ok = !differ( $out, $ref ); + ok( $ok, $file ); + unlink($out) if $ok; + } + + ok( $test++ == $numtests, "Tested $numtests files" ); +} diff --git a/t/cho/cho006.cho b/t/cho/cho006.cho new file mode 100644 index 00000000..3b6d7edc --- /dev/null +++ b/t/cho/cho006.cho @@ -0,0 +1,23 @@ +{title: Conditional directives} +{no_grid} + +{c-guitar: This is for guitar} +{c-guitar!: This is not for guitar} +{c-guitarx: This is for guitarx} +{c-guitarx!: This is not for guitarx} + +{start_of_whatever-guitar} +[A]This is for [B]Guitar +{end_of_whatever} + +{start_of_whatever-guitar!} +[A]This is not for [B]Guitar +{end_of_whatever} + +{start_of_whatever-guitarx} +[A]This is for [B]Guitarx +{end_of_whatever} + +{start_of_whatever-guitarx!} +[A]This is not for [B]Guitarx +{end_of_whatever} diff --git a/t/cho/cho006.ref b/t/cho/cho006.ref new file mode 100644 index 00000000..77673b4f --- /dev/null +++ b/t/cho/cho006.ref @@ -0,0 +1,15 @@ +{title: Conditional directives} +{no_grid} + +{comment: This is for guitar} +{comment: This is not for guitarx} + +{start_of_whatever} +[A]This is for [B]Guitar +{end_of_whatever} + + + +{start_of_whatever} +[A]This is not for [B]Guitarx +{end_of_whatever} diff --git a/t/latex/30_cho_1.cho b/t/latex/30_cho_1.cho new file mode 100644 index 00000000..76aa9e18 --- /dev/null +++ b/t/latex/30_cho_1.cho @@ -0,0 +1,41 @@ +{title: Swing Low Sweet Chariot} +{subtitle: Traditional} +{columns: 2} +{titles: center} +{define: D base-fret 1 frets X X 0 2 3 2 } +{define: G base-fret 1 frets 3 2 0 0 0 3 } +{define: A7 base-fret 1 frets X 0 2 0 2 0 } +{define: D7 base-fret 1 frets X X 0 2 1 2 } + +{start_of_chorus} +Swing [D]low, sweet [G]chari[D]ot, +Comin’ for to carry me [A7]home. +Swing [D7]low, sweet [G]chari[D]ot, +Comin’ for to [A7]carry me [D]home. +{end_of_chorus} + + +I [D]looked over Jordan, and [G]what did I [D]see, + “Comin’ for to carry me [A7]home.” +A [D7]band of angels [G]comin’ after [D]me, + Comin’ for to [A7]carry me [D]home. [G]     [D] [G] + +{new_page} +{comment: Chorus} + +If you get there before I do, + Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. + Comin’ for to carry me home. + +{column_break} +{comment: Chorus} + +# Note the ' here will be changed into a decent apostrophe. +I’m sometimes up and sometimes down, + Comin’ for to carry me home. +But still my soul feels heavenly bound. + Comin’ for to carry me home. + +{comment: Chorus} +{comment_italic: And a final chorus} diff --git a/t/latex/30_cho_1.tex b/t/latex/30_cho_1.tex new file mode 100644 index 00000000..26515db1 --- /dev/null +++ b/t/latex/30_cho_1.tex @@ -0,0 +1,47 @@ + +Traditional + + + +D XX0232 +G 320003 +A7 X02020 +D7 XX0212 +beginchorus + +wc Swing [D]low, sweet [G]chari[D]ot, +wc Comin{\textquoteright} for to carry me [A7]home. +wc Swing [D7]low, sweet [G]chari[D]ot, +wc Comin{\textquoteright} for to [A7]carry me [D]home. +endchorus + +beginverse + +wc I [D]looked over Jordan, and [G]what did I [D]see, +wc {\textquotedblleft}Comin{\textquoteright} for to carry me [A7]home.{\textquotedblright} +wc A [D7]band of angels [G]comin{\textquoteright} after [D]me, +wc Comin{\textquoteright} for to [A7]carry me [D]home. [G]~~~~~[D] [G] +endverse + +newpage +Chorus +beginverse + +If you get there before I do, + Comin{\textquoteright} for to carry me home. +Just tell my friends that I{\textquoteright}m a comin{\textquoteright} too. + Comin{\textquoteright} for to carry me home. +endverse + +colbreak +Chorus +beginverse + +I{\textquoteright}m sometimes up and sometimes down, + Comin{\textquoteright} for to carry me home. +But still my soul feels heavenly bound. + Comin{\textquoteright} for to carry me home. +endverse + +Chorus +\textit{And a final chorus} diff --git a/t/latex/30_cho_2.cho b/t/latex/30_cho_2.cho new file mode 100644 index 00000000..6d57f0fd --- /dev/null +++ b/t/latex/30_cho_2.cho @@ -0,0 +1,51 @@ +{title: Swing Low Sweet Chariot} +{subtitle: Traditional} +{lyricist: mylyricist} +{composer: mycomposer} + +{define: D base-fret 1 frets X X 0 2 3 2 } +{define: G base-fret 1 frets 3 2 0 0 0 3 } +{define: A7 base-fret 1 frets X 0 2 0 2 0 } +{define: D7 base-fret 1 frets X X 0 2 1 2 } + +{start_of_tab} +e|---R-----R-R--------| +B|-----M-------M---M--| +G|-------Z-------Z----| +D|---D-----D-D--------| +A|--------------------| +E|--------------------| +{eot} + +{start_of_chorus} +Swing [D]low, sweet [G]chari[D]ot, +Comin’ for to carry me [A7]home. +Swing [D7]low, sweet [G]chari[D]ot, +Comin’ for to [A7]carry me [D]home. +{end_of_chorus} + + +I [D]looked over Jordan, and [G]what did I [D]see, + “Comin’ for to carry me [A7]home.” +A [D7]band of angels [G]comin’ after [D]me, + Comin’ for to [A7]carry me [D]home. [G] [D] [G] + +{new_page} +{comment: Chorus} + +If you get there before I do, + Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. + Comin’ for to carry me home. + +{column_break} +{comment: Chorus} + +# Note the ' here will be changed into a decent apostrophe. +I’m sometimes up and sometimes down, + Comin’ for to carry me home. +But still my soul feels heavenly bound. + Comin’ for to carry me home. + +{comment: Chorus} +{comment_italic: And a final chorus} diff --git a/t/latex/30_cho_2.tex b/t/latex/30_cho_2.tex new file mode 100644 index 00000000..b729bfa9 --- /dev/null +++ b/t/latex/30_cho_2.tex @@ -0,0 +1,57 @@ + +Traditional +mycomposer +mylyricist + +D XX0232 +G 320003 +A7 X02020 +D7 XX0212 +begintab + +e|---R-----R-R--------| +B|-----M-------M---M--| +G|-------Z-------Z----| +D|---D-----D-D--------| +A|--------------------| +E|--------------------| +endtab + +beginchorus + +wc Swing [D]low, sweet [G]chari[D]ot, +wc Comin{\textquoteright} for to carry me [A7]home. +wc Swing [D7]low, sweet [G]chari[D]ot, +wc Comin{\textquoteright} for to [A7]carry me [D]home. +endchorus + +beginverse + +wc I [D]looked over Jordan, and [G]what did I [D]see, +wc {\textquotedblleft}Comin{\textquoteright} for to carry me [A7]home.{\textquotedblright} +wc A [D7]band of angels [G]comin{\textquoteright} after [D]me, +wc Comin{\textquoteright} for to [A7]carry me [D]home. [G] [D] [G] +endverse + +newpage +Chorus +beginverse + +If you get there before I do, + Comin{\textquoteright} for to carry me home. +Just tell my friends that I{\textquoteright}m a comin{\textquoteright} too. + Comin{\textquoteright} for to carry me home. +endverse + +colbreak +Chorus +beginverse + +I{\textquoteright}m sometimes up and sometimes down, + Comin{\textquoteright} for to carry me home. +But still my soul feels heavenly bound. + Comin{\textquoteright} for to carry me home. +endverse + +Chorus +\textit{And a final chorus} diff --git a/t/latex/t_comment.tt b/t/latex/t_comment.tt new file mode 100644 index 00000000..8097cd9f --- /dev/null +++ b/t/latex/t_comment.tt @@ -0,0 +1 @@ +[% comment %] diff --git a/t/latex/t_config.json b/t/latex/t_config.json new file mode 100644 index 00000000..5f3b6ff3 --- /dev/null +++ b/t/latex/t_config.json @@ -0,0 +1,10 @@ +{ + "latex" : { // please be aware that LaTeX Song Package require Verse + "template_include_path" : ["./latex/"], + "templates" : { + "songbook" : "t_songbook.tt", + "comment" : "t_comment.tt", + "image" : "t_image.tt" + } + } +} \ No newline at end of file diff --git a/t/latex/t_image.tt b/t/latex/t_image.tt new file mode 100644 index 00000000..2e1b8e79 --- /dev/null +++ b/t/latex/t_image.tt @@ -0,0 +1 @@ +[% uri %] \ No newline at end of file diff --git a/t/latex/t_songbook.tt b/t/latex/t_songbook.tt new file mode 100644 index 00000000..a1e1e85c --- /dev/null +++ b/t/latex/t_songbook.tt @@ -0,0 +1,42 @@ +[% # these variables are obligatory to set + +newpage_tag = "newpage\n"; +emptyline_tag = "emptyline\n"; +columnbreak_tag = "colbreak\n"; +beginchorus_tag = "beginchorus\n"; +endchorus_tag = "endchorus\n"; +beginverse_tag = "beginverse\n"; +endverse_tag = "endverse\n"; +begingrid_tag = "begingrid\n"; +endgrid_tag = "endgrid\n"; +begintab_tag = "begintab\n"; +endtab_tag = "endtab\n"; +gchordstart_tag = "["; +gchordend_tag = "]"; +chorded_line = "wc "; +unchorded_line = ""; +start_spaces_songline = " "; +eol = "\n"; +beginabc_tag = "begin_abc\n"; +endabc_tag = "end_abc\n"; +beginlilypond_tag = "begin_lilypond\n"; +endlilypond_tag = "end_lilypond\n"; + +FOREACH song IN songs; + title; "\n"; + FOREACH st IN song.subtitle; + st; "\n"; + END; + song.meta.composer.0; "\n"; + song.meta.lyricist.0; "\n"; + song.meta.copyright.0; "\n"; + IF song.meta.capo.0; + "Capo "; song.meta.capo.0; "\n"; + END; + FOREACH chord IN song.chords; + chord.chord; " "; chord.frets; "\n"; + END; + song.songlines | eval; +END; + +-%] diff --git a/t/md/30_cho_1.cho b/t/md/30_cho_1.cho new file mode 100644 index 00000000..76aa9e18 --- /dev/null +++ b/t/md/30_cho_1.cho @@ -0,0 +1,41 @@ +{title: Swing Low Sweet Chariot} +{subtitle: Traditional} +{columns: 2} +{titles: center} +{define: D base-fret 1 frets X X 0 2 3 2 } +{define: G base-fret 1 frets 3 2 0 0 0 3 } +{define: A7 base-fret 1 frets X 0 2 0 2 0 } +{define: D7 base-fret 1 frets X X 0 2 1 2 } + +{start_of_chorus} +Swing [D]low, sweet [G]chari[D]ot, +Comin’ for to carry me [A7]home. +Swing [D7]low, sweet [G]chari[D]ot, +Comin’ for to [A7]carry me [D]home. +{end_of_chorus} + + +I [D]looked over Jordan, and [G]what did I [D]see, + “Comin’ for to carry me [A7]home.” +A [D7]band of angels [G]comin’ after [D]me, + Comin’ for to [A7]carry me [D]home. [G]     [D] [G] + +{new_page} +{comment: Chorus} + +If you get there before I do, + Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. + Comin’ for to carry me home. + +{column_break} +{comment: Chorus} + +# Note the ' here will be changed into a decent apostrophe. +I’m sometimes up and sometimes down, + Comin’ for to carry me home. +But still my soul feels heavenly bound. + Comin’ for to carry me home. + +{comment: Chorus} +{comment_italic: And a final chorus} diff --git a/t/md/30_cho_1.md b/t/md/30_cho_1.md new file mode 100644 index 00000000..b4868ec0 --- /dev/null +++ b/t/md/30_cho_1.md @@ -0,0 +1,46 @@ +# Swing Low Sweet Chariot +## Traditional +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![G](https://chordgenerator.net/G.png?p=320003&s=2) ![A7](https://chordgenerator.net/A7.png?p=x02020&s=2) ![D7](https://chordgenerator.net/D7.png?p=xx0212&s=2) + +**Chorus** + + D G D + Swing low, sweet chariot, + A7 + Comin’ for to carry me home. + D7 G D + Swing low, sweet chariot, + A7 D + Comin’ for to carry me home. + +--------------- + + D G D + I looked over Jordan, and what did I see, + A7 + “Comin’ for to carry me home.” + D7 G D + A band of angels comin’ after me, + A7 D G D G + Comin’ for to carry me home.       -- + +--------------- + +> Chorus + +If you get there before I do, +     Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. +     Comin’ for to carry me home. + +> Chorus + +I’m sometimes up and sometimes down, +     Comin’ for to carry me home. +But still my soul feels heavenly bound. +     Comin’ for to carry me home. + +> Chorus +> *And a final chorus* +--------------- + diff --git a/t/md/30_cho_2.cho b/t/md/30_cho_2.cho new file mode 100644 index 00000000..76aa9e18 --- /dev/null +++ b/t/md/30_cho_2.cho @@ -0,0 +1,41 @@ +{title: Swing Low Sweet Chariot} +{subtitle: Traditional} +{columns: 2} +{titles: center} +{define: D base-fret 1 frets X X 0 2 3 2 } +{define: G base-fret 1 frets 3 2 0 0 0 3 } +{define: A7 base-fret 1 frets X 0 2 0 2 0 } +{define: D7 base-fret 1 frets X X 0 2 1 2 } + +{start_of_chorus} +Swing [D]low, sweet [G]chari[D]ot, +Comin’ for to carry me [A7]home. +Swing [D7]low, sweet [G]chari[D]ot, +Comin’ for to [A7]carry me [D]home. +{end_of_chorus} + + +I [D]looked over Jordan, and [G]what did I [D]see, + “Comin’ for to carry me [A7]home.” +A [D7]band of angels [G]comin’ after [D]me, + Comin’ for to [A7]carry me [D]home. [G]     [D] [G] + +{new_page} +{comment: Chorus} + +If you get there before I do, + Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. + Comin’ for to carry me home. + +{column_break} +{comment: Chorus} + +# Note the ' here will be changed into a decent apostrophe. +I’m sometimes up and sometimes down, + Comin’ for to carry me home. +But still my soul feels heavenly bound. + Comin’ for to carry me home. + +{comment: Chorus} +{comment_italic: And a final chorus} diff --git a/t/md/30_cho_2.md b/t/md/30_cho_2.md new file mode 100644 index 00000000..b4868ec0 --- /dev/null +++ b/t/md/30_cho_2.md @@ -0,0 +1,46 @@ +# Swing Low Sweet Chariot +## Traditional +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![G](https://chordgenerator.net/G.png?p=320003&s=2) ![A7](https://chordgenerator.net/A7.png?p=x02020&s=2) ![D7](https://chordgenerator.net/D7.png?p=xx0212&s=2) + +**Chorus** + + D G D + Swing low, sweet chariot, + A7 + Comin’ for to carry me home. + D7 G D + Swing low, sweet chariot, + A7 D + Comin’ for to carry me home. + +--------------- + + D G D + I looked over Jordan, and what did I see, + A7 + “Comin’ for to carry me home.” + D7 G D + A band of angels comin’ after me, + A7 D G D G + Comin’ for to carry me home.       -- + +--------------- + +> Chorus + +If you get there before I do, +     Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. +     Comin’ for to carry me home. + +> Chorus + +I’m sometimes up and sometimes down, +     Comin’ for to carry me home. +But still my soul feels heavenly bound. +     Comin’ for to carry me home. + +> Chorus +> *And a final chorus* +--------------- + diff --git a/t/md/30_cho_3.cho b/t/md/30_cho_3.cho new file mode 100644 index 00000000..f9c4db51 --- /dev/null +++ b/t/md/30_cho_3.cho @@ -0,0 +1,41 @@ +{title: Swing Low Sweet Chariot} +{subtitle: Traditional} +{columns: 2} +{titles: center} +{define: D base-fret 1 frets X X 0 2 3 2 } +{define: G base-fret 1 frets 3 2 0 0 0 3 } +{define: A7 base-fret 1 frets X 0 2 0 2 0 } +{define: D7 base-fret 1 frets X X 0 2 1 2 } + +{start_of_chorus} +Swing low, sweet chariot, +Comin’ for to carry me home. +Swing low, sweet chariot, +Comin’ for to carry me home. +{end_of_chorus} + + +I looked over Jordan, and what did I see, + “Comin’ for to carry me home.” +A band of angels comin’ after me, + Comin’ for to carry me home.       + +{new_page} +{comment: Chorus} + +If you get there before I do, + Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. + Comin’ for to carry me home. + +{column_break} +{comment: Chorus} + +# Note the ' here will be changed into a decent apostrophe. +I’m sometimes up and sometimes down, + Comin’ for to carry me home. +But still my soul feels heavenly bound. + Comin’ for to carry me home. + +{comment: Chorus} +{comment_italic: And a final chorus} diff --git a/t/md/30_cho_3.md b/t/md/30_cho_3.md new file mode 100644 index 00000000..a090f33a --- /dev/null +++ b/t/md/30_cho_3.md @@ -0,0 +1,37 @@ +# Swing Low Sweet Chariot +## Traditional + +**Chorus** + +Swing low, sweet chariot, +Comin’ for to carry me home. +Swing low, sweet chariot, +Comin’ for to carry me home. + +--------------- + +I looked over Jordan, and what did I see, +     “Comin’ for to carry me home.” +A band of angels comin’ after me, +     Comin’ for to carry me home. + +--------------- + +> Chorus + +If you get there before I do, +     Comin’ for to carry me home. +Just tell my friends that I’m a comin’ too. +     Comin’ for to carry me home. + +> Chorus + +I’m sometimes up and sometimes down, +     Comin’ for to carry me home. +But still my soul feels heavenly bound. +     Comin’ for to carry me home. + +> Chorus +> *And a final chorus* +--------------- + diff --git a/t/md/a34.cho b/t/md/a34.cho new file mode 100644 index 00000000..1e4bff3e --- /dev/null +++ b/t/md/a34.cho @@ -0,0 +1,15 @@ +{title: a34} +{key: C} +{time: 3/4} +{tempo: 100} +{duration: 16} + +{comment: A} +{start_of_grid 4x6} +| C . . | F . . | G . . | C . . | +| . . . | F . . | G . . | +{end_of_grid} +{start_of_grid 4x5} +| C . Am | +{end_of_grid} + diff --git a/t/md/a34.md b/t/md/a34.md new file mode 100644 index 00000000..afd35e29 --- /dev/null +++ b/t/md/a34.md @@ -0,0 +1,13 @@ +# a34 +![C](https://chordgenerator.net/C.png?p=x32010&s=2) ![F](https://chordgenerator.net/F.png?p=133211&s=2) ![G](https://chordgenerator.net/G.png?p=320003&s=2) ![Am](https://chordgenerator.net/Am.png?p=x02210&s=2) + +> A +**Grid** + + |C..|F..|G..|C..| + |...|F..|G..| + + |C.Am| + +--------------- + diff --git a/t/md/a44.cho b/t/md/a44.cho new file mode 100644 index 00000000..8b0506cc --- /dev/null +++ b/t/md/a44.cho @@ -0,0 +1,15 @@ +{title: a44} +{key: C} +{time: 4/4} +{tempo: 100} +{duration: 21} + +{comment: A} +{start_of_grid 4x8} +| C . . . | F . . . | G . . . | C . . . | +| . . . . | F . . . | G . . . | +{end_of_grid} +{start_of_grid 4x7} +| C . Am . | +{end_of_grid} + diff --git a/t/md/a44.md b/t/md/a44.md new file mode 100644 index 00000000..4985b8c7 --- /dev/null +++ b/t/md/a44.md @@ -0,0 +1,13 @@ +# a44 +![C](https://chordgenerator.net/C.png?p=x32010&s=2) ![F](https://chordgenerator.net/F.png?p=133211&s=2) ![G](https://chordgenerator.net/G.png?p=320003&s=2) ![Am](https://chordgenerator.net/Am.png?p=x02210&s=2) + +> A +**Grid** + + |C...|F...|G...|C...| + |....|F...|G...| + + |C.Am.| + +--------------- + diff --git a/t/md/cho001.cho b/t/md/cho001.cho new file mode 100644 index 00000000..1a67c5b0 --- /dev/null +++ b/t/md/cho001.cho @@ -0,0 +1,15 @@ +{title: t1} +{artist: Foo} +{artist: Bar} +{key: D} + +{c: Key: %{key}} +[D]Hello[*Bis] +{chord B frets 1 2 3 4 5 6} +Air [(E)]Hello[E] +{chord B} +S[NC]ure + +#{start_of_grid 1+4x4+2} +#A | D . . . | E . . . | (D) . . . | E . . . | +#{end_of_grid} diff --git a/t/md/cho001.md b/t/md/cho001.md new file mode 100644 index 00000000..eea245dd --- /dev/null +++ b/t/md/cho001.md @@ -0,0 +1,16 @@ +# t1 +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![E](https://chordgenerator.net/E.png?p=022100&s=2) ![NC](https://chordgenerator.net/NC.png?p=xxxxxx&s=2) + +> Key: D + + D *Bis + Hello----- + + (E) E + Air Hello-- + + NC + Sure + +--------------- + diff --git a/t/md/cho002.cho b/t/md/cho002.cho new file mode 100644 index 00000000..3d9ab811 --- /dev/null +++ b/t/md/cho002.cho @@ -0,0 +1,11 @@ +{title: Keys} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +{key: D} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi +{transpose 2} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi +{transpose} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi diff --git a/t/md/cho002.md b/t/md/cho002.md new file mode 100644 index 00000000..fcadf319 --- /dev/null +++ b/t/md/cho002.md @@ -0,0 +1,21 @@ +# Keys +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![E](https://chordgenerator.net/E.png?p=022100&s=2) + +> Key: , Actual: , From: +> Key: D, Actual: D, From: + + D + Hi + +> Key: D, Actual: E, From: D + + E + Hi + +> Key: D, Actual: D, From: + + D + Hi + +--------------- + diff --git a/t/md/cho003.cho b/t/md/cho003.cho new file mode 100644 index 00000000..336d27f5 --- /dev/null +++ b/t/md/cho003.cho @@ -0,0 +1,5 @@ +{title: https://groups.io/g/ChordPro/message/1092} + +[Bb6] comes out as Bb6 +[Bb7] comes out as A#7 +[Bb] comes out as A# diff --git a/t/md/cho003.md b/t/md/cho003.md new file mode 100644 index 00000000..f097b779 --- /dev/null +++ b/t/md/cho003.md @@ -0,0 +1,12 @@ +# https://groups.io/g/ChordPro/message/1092 +![Bb6](https://chordgenerator.net/Bb6.png?p=xx3333&s=2) ![Bb7](https://chordgenerator.net/Bb7.png?p=xx1112&s=2) ![Bb](https://chordgenerator.net/Bb.png?p=x13331&s=2) + + Bb6 + comes out as Bb6 + Bb7 + comes out as A#7 + Bb + comes out as A# + +--------------- + diff --git a/t/md/cho004.cho b/t/md/cho004.cho new file mode 100644 index 00000000..d25ad0dd --- /dev/null +++ b/t/md/cho004.cho @@ -0,0 +1,18 @@ +{title Tranpose} + +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose +4} +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose +2} +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose -2} +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose} +# Back to +4 +2 +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose} +# Back to +4 +I [D]looked over Jordan, and [G]what did I [D]see, +{transpose} +# Back to default +I [D]looked over Jordan, and [G]what did I [D]see, diff --git a/t/md/cho004.md b/t/md/cho004.md new file mode 100644 index 00000000..056e6706 --- /dev/null +++ b/t/md/cho004.md @@ -0,0 +1,23 @@ +# Tranpose +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![G](https://chordgenerator.net/G.png?p=320003&s=2) ![F#](https://chordgenerator.net/F#.png?p=244322&s=2) ![B](https://chordgenerator.net/B.png?p=x24442&s=2) ![G#](https://chordgenerator.net/G#.png?p=133211&s=2) ![C#](https://chordgenerator.net/C#.png?p=xx3121&s=2) ![Gb](https://chordgenerator.net/Gb.png?p=244322&s=2) + + D G D + I looked over Jordan, and what did I see, + F# B F# + I looked over Jordan, and what did I see, + G# C# G# + I looked over Jordan, and what did I see, + Gb B Gb + I looked over Jordan, and what did I see, + + G# C# G# + I looked over Jordan, and what did I see, + + F# B F# + I looked over Jordan, and what did I see, + + D G D + I looked over Jordan, and what did I see, + +--------------- + diff --git a/t/md/cho005.cho b/t/md/cho005.cho new file mode 100644 index 00000000..b8c3bb76 --- /dev/null +++ b/t/md/cho005.cho @@ -0,0 +1,11 @@ +{title: Keys and Capo} +{key: D} +{capo: 2} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi +{transpose 3} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi +{transpose} +{c: Key: %{key}, Actual: %{key_actual}, From: %{key_from}} +[D]Hi diff --git a/t/md/cho005.md b/t/md/cho005.md new file mode 100644 index 00000000..9c12e03b --- /dev/null +++ b/t/md/cho005.md @@ -0,0 +1,20 @@ +# Keys and Capo +![D](https://chordgenerator.net/D.png?p=xx0232&s=2) ![F](https://chordgenerator.net/F.png?p=133211&s=2) + +> Key: D, Actual: E, From: D + + D + Hi + +> Key: D, Actual: G, From: E + + F + Hi + +> Key: D, Actual: D, From: + + D + Hi + +--------------- + diff --git a/t/ref/40_html_1.html b/t/ref/40_html_1.html index 9334450b..1083297b 100644 --- a/t/ref/40_html_1.html +++ b/t/ref/40_html_1.html @@ -49,6 +49,7 @@ +
@@ -81,7 +82,9 @@
D G D Comin’ for to carry me home.
+
Chorus
+
@@ -114,7 +117,9 @@
Comin’ for to carry me home.
+
Chorus
+
@@ -147,6 +152,7 @@
Comin’ for to carry me home.
+
Chorus
And a final chorus
diff --git a/t/ref/40_html_2.html b/t/ref/40_html_2.html index ca52f80b..18f5082c 100644 --- a/t/ref/40_html_2.html +++ b/t/ref/40_html_2.html @@ -49,6 +49,7 @@ +
@@ -81,7 +82,9 @@
D G D Comin’ for to carry me home.
+
Chorus
+
@@ -102,7 +105,9 @@
If you get there before I do, Comin’ for to carry me home.
+
Chorus
+
@@ -123,6 +128,7 @@
I’m sometimes up and sometimes down, Comin’ for to carry me home.
+
Chorus
And a final chorus
diff --git a/t/ref/40_html_3.html b/t/ref/40_html_3.html index 56965422..8c450511 100644 --- a/t/ref/40_html_3.html +++ b/t/ref/40_html_3.html @@ -37,6 +37,7 @@ +
@@ -57,7 +58,9 @@
I looked over Jordan, and what did I see, Comin’ for to carry me home.
+
Chorus
+
@@ -78,7 +81,9 @@
If you get there before I do, Comin’ for to carry me home.
+
Chorus
+
@@ -99,6 +104,7 @@
I’m sometimes up and sometimes down, Comin’ for to carry me home.
+
Chorus
And a final chorus
diff --git a/wiki/images/GNUmakefile b/wiki/images/GNUmakefile index 65508554..be076f60 100644 --- a/wiki/images/GNUmakefile +++ b/wiki/images/GNUmakefile @@ -25,7 +25,7 @@ page_%.png : page_%.cho #### Styles. -STYLES := modern1 modern2 modern3 chordii default dark roman nashville ukulele keyboard +STYLES := modern1 modern2 modern3 chordii dark roman nashville ukulele keyboard styles : $(foreach sty,${STYLES},style_${sty}.png) diff --git a/wiki/images/basic-layout-large.png b/wiki/images/basic-layout-large.png deleted file mode 100644 index 3ed478c7..00000000 Binary files a/wiki/images/basic-layout-large.png and /dev/null differ diff --git a/wiki/images/basic-layout.png b/wiki/images/basic-layout.png deleted file mode 100644 index 2f91b18e..00000000 Binary files a/wiki/images/basic-layout.png and /dev/null differ diff --git a/wiki/images/pageformats.json b/wiki/images/pageformats.json index 6c14058a..78a12d67 100644 --- a/wiki/images/pageformats.json +++ b/wiki/images/pageformats.json @@ -1,183 +1,13 @@ // Configuration for ChordPro. -// -// This is a relaxed JSON document, so comments are possible. { - // General settings, to be changed by legacy configs and - // command line. - "settings" : { - // Titles flush: default center. - "titles" : "center", - // Columns, default one. - "columns" : 1, - // Suppress empty chord lines. - // Overrides the -a (--single-space) command line options. - "suppress-empty-chords" : true, - // Suppress chords. - // Overrides --lyrics-only command line option. - "lyrics-only" : false, - // Chords inline. - // May be a string containing pretext %s posttext. - // Defaults to "[%s]" if true. - "inline-chords" : false, - }, - - // Metadata. - // For these keys you can use {meta key ...} as well as {key ...}. - // If strict is nonzero, only the keys named here are allowed. - // If strict is zero, {meta ...} will accept any key. - // Important: "title" and "subtitle" must always be in this list. - // The separator is used to concatenate multiple values. - "metadata" : { - "keys" : [ "title", "subtitle", - "artist", "composer", "lyricist", "arranger", - "album", "copyright", "year", - "key", "time", "tempo", "capo", "duration" ], - "strict" : true, - "separator" : "; ", - }, - - // Strings and tuning. - // Note that using this will discard all built-in chords! - // "tuning" : [ "E2", "A2", "D3", "G3", "B3", "E4" ], - "tuning" : null, - - // User defined chords. - // "base" defaults to 1. - // "easy" is ignored. - // Use 0 for an empty string, and -1 for a muted string. - "chords" : [ - // { - // "name" : "Bb", - // "base" : 1, - // "frets" : [ 1, 1, 3, 3, 3, 1 ], - // "fingers" : [ 1, 1, 2, 3, 4, 1 ], - // "easy" : true, - // }, - ], - - // Printing chord diagrams. - // "auto": automatically add unknown chords as empty diagrams. - // "show": prints the chords used in the song. - // "all": all chords used. - // "user": only prints user defined chords. - // "sorted": order the chords by key. - "diagrams" : { - "auto" : false, - "show" : "all", - "sorted" : false, - }, - - // Diagnostig messages. - "diagnostics" : { - "format" : "\"%f\", line %n, %m\n\t%l", - }, - - // Table of contents. - "toc" : { - // Title for ToC. - "title" : "Table of Contents", - // Sorting order. - // Currently only sorting by page number and alpha is implemented. - "order" : "page", - }, // Layout definitions for PDF output. "pdf" : { - // Papersize, 'a4' or [ 595, 842 ] etc. - "papersize" : "a4", - - // Space between columns, in pt. - "columnspace" : 20, - - // Page margins. - // Note that top/bottom exclude the head/footspace. "margintop" : 100, - "marginbottom" : 40, - "marginleft" : 40, - "marginright" : 40, "headspace" : 80, - "footspace" : 20, - - // Special: head on first page only, add the headspace to - // the other pages so they become larger. - "head-first-only" : false, - - // Spacings. - // Baseline distances as a factor of the font size. - "spacing" : { - "title" : 1.2, - "lyrics" : 1.2, - "chords" : 1.2, - "grid" : 1.2, - "tab" : 1.0, - "toc" : 1.4, - "empty" : 1.0, - }, - // Note: By setting the font size and spacing for empty lines to - // smaller values, you get a fine(r)-grained control over the - // spacing between the various parts of the song. - - // Style of chorus. - "chorus" : { - "indent" : 0, - // Chorus side bar. - // Suppress by setting offset and/or width to zero. - "bar" : { - "offset" : 8, - "width" : 1, - "color" : "black", - }, - "tag" : "Chorus", - // Recall style: Print the tag using the type. - // Optionally quote the lines of the preceding chorus. - "recall" : { - "tag" : "Chorus", - "type" : "comment", - "quote" : false, - }, - }, - - // Indent for lyrics. - // "indent" : 70, - "indent" : 0, - - // Alternative songlines with chords in a side column. - // Value is the column position. - // "chordscolumn" : 400, - "chordscolumn" : 0, - - // A {titles: left} may conflict with customized formats. - // Set to non-zero to ignore the directive. - "titles-directive-ignore" : false, - - // Chord diagrams. - // A chord diagram consists of a number of cells. - // Cell dimensions are specified by "width" and "height". - // The horizontal number of cells depends on the number of strings. - // The vertical number of cells is "vcells", which should - // be 4 or larger to accomodate most chords. - // The horizontal distance between diagrams is "hspace" cells. - // The vertical distance is "vspace" cells. - // "linewidth" is the thickness of the lines as a fraction of "width". - // Diagrams for all chords of the song can be shown at the - // "top" of the first page, the "bottom" of the last page - // (default), "left" on the first page and "right" on the first - // page. "left" is not implemented. - "diagrams" : { - "show" : "bottom", - "width" : 15, - "height" : 15, - "hspace" : 10, - "vspace" : 3, - "vcells" : 4, - "linewidth" : 0.1, - }, - - // Even/odd pages. A value of -1 denotes odd/even pages. - "even-odd-pages" : 1, // Formats. "formats" : { @@ -194,6 +24,8 @@ "subtitle" : null, // Footer is title -- page number. "footer" : [ "%{title}", "", "%{page}" ], + // Background pages: 5 and 6 from bgdemo. + "background" : "examples/bgdemo.pdf:5", }, // The first page of a song has: "title" : { @@ -202,81 +34,44 @@ "subtitle" : [ "", "%{subtitle}", "" ], // Footer with page number. "footer" : [ "", "", "%{page}" ], - }, + // Background pages: 3 and 4 from bgdemo. + "background" : "examples/bgdemo.pdf:3", + }, // The very first output page is slightly different: "first" : { // It has title and subtitle, like normal 'first' pages. // But no footer. "footer" : null, + // Background pages: 1 and 2 from bgdemo. + "background" : "examples/bgdemo.pdf:1", }, }, - // Fonts. - // Fonts can be specified by name (for the corefonts) - // or a filename (for TrueType/OpenType fonts). - // Relative filenames are looked up in the fontdir. - // "fontdir" : [ "/usr/share/fonts/liberation", "/home/me/fonts" ], - "fontdir" : null, - - // Fonts for chords and comments can have a background - // colour associated. - // Colours are "#RRGGBB" or predefined names like "black", "white", - // and lots of others. + // Diagrams. + "diagrams" : { + "height" : "15", + "hspace" : "10", + "width" : "15" + }, + // Fonts. "fonts" : { "title" : { - "name" : "Times-Bold", "size" : 30 }, "subtitle" : { - "name" : "Times", "size" : 24 }, "footer" : { - "name" : "Times", "size" : 24 }, "text" : { - "name" : "Times-Roman", "size" : 20 }, "chord" : { - "name" : "Helvetica-Oblique", "size" : 20 }, - "comment" : { - "name" : "Helvetica", - "size" : 12 - }, - "tab" : { - "name" : "Courier", - "size" : 10 - }, - "toc" : { - "name" : "Times-Roman", - "size" : 11 - }, - "grid" : { - "name" : "Helvetica", - "size" : 10 - }, }, - - // Fonts that can be specified, but need not. - // subtitle --> text - // comment --> text - // comment_italic --> chord - // comment_box --> chord - // toc --> text - // grid --> chord - // grid_margin --> comment - // footer --> subtitle @ 60% - // empty --> text - // diagram --> comment - // diagram_base --> text (but at a small size) - - // This will show the page layout if non-zero. - "showlayout" : false, }, } // End of config.