跳转到内容

谱面与页面排版

完成上一章的学习后,我们已经可以书写单声部的基本旋律了。本章将正式介绍如何将旋律分为多行、展示在谱面上,再用 @page 设置页面,了解内容怎样分页,以及怎样控制谱面行自动撑满。

源码换行不一定让谱面换行。 普通音符之间的单次换行只是分隔内容;N: 后的换行用于结束这段声部内容,L: 后的换行用于结束这行歌词,也不会因此另起一行谱。

要明确让后面的内容在谱面中另起一行,请使用函数 @br:

@br(offset=1)

offset 表示向后偏移几行,默认是 1。@br() 等价于 @br(1),从下一行继续;@br(2) 则在中间空出一行。换行只改变谱面排版,不占音乐时值。

在同一个声部内换行,歌词随音符排列
N(旋律): 1 2 @br(2) 3 4
L(歌词): 微风轻轻
复制代码

这里四个音符都在同一条 N: 源码行中,因此都属于同一个声部。@br() 把 3 4 排到下一行,“轻轻”也随之排到对应音符下方。不过这种把换行写在声部内写法是不建议的,特别是后面学习了多声部后。

在顶层留空行(连续两个及以上的换行),就相当于 @br(1)。一次性空100行也会被认为是一次换行。如果需要一次性换出多行谱面,还是需要使用函数调用的形式。

“顶层”指的是不在任何函数或内容块 {} 内的源码。只有顶层的空行才会被视为谱面换行的简写;在函数内容内部,空行只是源码排版,不会影响谱面。这是函数式语法的硬伤:无视格式和缩进。“在顶层生效”是为了记谱方便最后的倔强,这个语法糖是不方便拓展到更深层级中的。

用源码顶层中的空行让谱面换行
% 等价于 1 2 | @br() 3 4 |
1 2 |

3 4 |

{   % 内容块中就不是顶层了
1 2 |

3 4 |   % 空行不会生效,依旧在同一行
}
复制代码

@br 决定在哪里换行,@page 则决定这些谱面行放在多大的页面上,以及四周留多少空白:

@page(
width=794px,
height=0px,
top=45px,
bottom=48px,
left=40px,
right=40px,
gap=1em,
numbering="",
fillRatio=0.5
)

上面是各参数的默认值。只需要填写要修改的参数,其他参数会保留默认值:

参数作用
width页面宽度,必须大于 0 和左右边距
height页面高度;小于等于 0px 表示不自动分页,页面随内容向下延长(仍可手动换页)
top、bottom上、下边距
left、right左、右边距
gap相邻谱面行之间的最小间距,不是同一行中各个声部之间的距离
numbering页码格式,空字符串表示不显示页码
fillRatio自然宽度达到多少倍版心宽度时横向撑满,默认 0.5

长度可以使用 px 或 em,其中 em 相对于声明 @page 时的字号。边距和 gap 不能为负,扣除边距后也必须留下可用的内容区域。例如宽度 320px、左右各留 20px,谱面行实际可用的宽度就是 280px。

设置页面尺寸
@page(width=100px, left=20px, right=20px, top=16px, bottom=16px, gap=2em)
1 2 3 5 | @br() 4 3 2 1 0 |||
复制代码

这里没有设置有限的 height,所以两行仍放在同一张随内容增高的页面上。不写 @page 时也不会自动分页,并不是默认按 A4 高度分页。

@page 只能在文档顶层声明,并且一份文档只有第一条有效,因此通常把它放在曲谱开头。@page 是整份文档的设置,不是“从这里开始新的一页”的命令。 jpFun 不支持中途改变页面尺寸。

把 numbering 设为 "1 / 1",就会在每页下边距内居中显示“当前页 / 总页数”。格式中的 1 是占位符:只有一个时表示当前页;有多个时,最后一个表示总页数,前面的表示当前页。因此三页文档会依次显示 1 / 3、2 / 3、3 / 3。

启用页码时,bottom 至少需要 16px,否则会因放不下页码而报错。

需要固定高度的页面时,把 height 设为正值。分页时,jpFun 会先根据 @br 和顶层空行确定谱面行,再计算每行所需的高度。歌词、连音线等内容都会影响行高。扣除上下边距后,一页能放多少行取决于这些实际高度与 gap,不是固定的行数。

  • 从页顶开始依次放入整行,相邻行之间至少留出 gap。
  • 如果下一行放不下,就把它整体放到下一页,不会从中间切开音符行或把歌词留在上一页。
  • 因为放不下下一行而结束的页面,会把剩余高度均分到各行下方,让行与行之间更疏一些,并在最后一行下方也留出一份空白;第一行仍从上边距开始。
  • 最后一页不做这种纵向撑开,只保留最小行距 gap;即使内容很少,页面本身仍保持设定的高度。
  • 如果单独一行就高于整页的可用高度,会报错,而不是缩小整行。此时需要增加页面高度、减小字号或调整内容。

下面故意使用很矮的页面,便于看清分页效果。六行音符每页放两行,最终得到三页:

按整行自动分页,并显示页码
@page(width=200px, height=100px, top=10px, bottom=20px,
    left=20px, right=20px, gap=5px, numbering="第 1 页 / 共 1 页")
1 2 3 4 @br()
2 3 4 5 @br()
3 4 5 6 @br()
4 5 6 7 @br()
5 6 7 1' @br()
6 7 1' 2'
复制代码

自动分页不等于自动换行。 一行写得太长时,jpFun 不会自动挑选某根小节线折到下一行;仍应由你用 @br 或空行划分谱面行,再交给页面布局决定它们放在哪一页。

@pagebreak()(别名 @pb())立即结束当前页,让后面的内容从新页第一行继续,不需要先写 @br(),也不占音乐时值:

@page(height=1123px)
1 2 @br() 3 4 @pb() 5 6

只有一个参数 justify,默认为 true。有限页高下,即使当前页没有装满,也会按上面的规则分配剩余空间;@pb(justify=false) 则只保留最小行距,把余白留在页底。无论取哪个值,新页都从上边距开始。没有设置有限页高时,当前页按自然高度结束,没有多余高度可以分配。

每次调用都严格新开一页:开头调用会留下空白首页,连续调用会留下中间空白页,末尾调用也会保留新开的空白尾页。有限页高的空白页保持整页高度;自然高度的空白页只保留上下边距。空白页同样计入页码。

多声部在同一时刻调用时,全谱只换一次页;只要其中一个调用指定了 justify=false,当前页就不拉伸。同一声部连续调用则分别执行。不要再为同一个边界额外写 @br(),否则仍会多偏移一行。

可以用 @set(pagebreak.justify=false) 修改后续调用的默认值,别名写法 @set(pb.justify=false) 也会归一到主名称。显式传入的参数优先于默认设置。

jpFun 的横向排版算法使用弹簧模型,每个元素有自己的宽度(称之为“自然宽距”)。如果排成一行发现放不进纸面,就会相互挤压,此时弹簧算法能得到美观的排布。但是如果本就放得下、不需要挤压呢?为了美观,jpFun 有以下规则:

  • 内容按自然宽度排开后,不足可用宽度的 fillRatio,就保留紧凑排布,不为了对齐右边界而大幅拉开。比如说最后一小节单独成行时。
  • 达到或超过 fillRatio,就把剩余宽度分配到可伸展的间隙里,让这一行撑满可用宽度。

默认值 0.5 就是“满半行后撑满”。设为负数会让所有行都撑满;设为 1 或更大时,等价于不会自动拉开撑满。

短行保留自然间距,满半行后自动撑满
@page(width=320px, left=20px, right=20px, top=16px, bottom=16px, fillRatio=0.5)
1 2 @br()
1 2 3 4 5 6 7 1'
复制代码

第一行只有两个音符,保持紧凑;第二行内容达到半行,音符间距被拉开。

这要求你在排版时考虑每行的自然宽度,合理安排换行位置。

  • @br() 让后面的内容另起一行,@br(2) 在中间空出一行。
  • 顶层空行是 @br() 的简写,单次源码换行不等于谱面换行;函数内容内部需要显式写 @br()。
  • @page 在顶层设置整份文档的尺寸、边距、最小行距、横向撑满比例和页码,只有第一条声明有效。
  • height <= 0px 不自动分页,会自适应高度;有限高度下按整行自动分页,末页保留最小行距,一行本身放不下时会报错。
  • @pagebreak() / @pb() 强制新开一页;justify=false 不分配当前页的剩余空间,开头、连续和末尾调用均保留空白页。
  • 一行自然宽度达到可用宽度的 fillRatio(默认0.5)后,会拉开间距撑满;否则保持自然宽度排布。

下一章 多声部与临时多声部 将介绍多个声部如何同时演奏,以及怎样在旋律中临时叠加另一段音符。