diff --git a/data/themes/default-theme.yml b/data/themes/default-theme.yml
index 301a21fa3..ac3f781d8 100644
--- a/data/themes/default-theme.yml
+++ b/data/themes/default-theme.yml
@@ -25,6 +25,9 @@ page:
background_color: ffffff
layout: portrait
margin: [0.5in, 0.67in, 0.67in, 0.67in]
+ # margin_inner and margin_outer keys are used for recto/verso print margins when media=press
+ margin_inner: 0.75in
+ margin_outer: 0.59in
size: A4
base:
align: justify
diff --git a/docs/theming-guide.adoc b/docs/theming-guide.adoc
index 1071602fc..51de9cf8a 100644
--- a/docs/theming-guide.adoc
+++ b/docs/theming-guide.adoc
@@ -18,6 +18,7 @@ endif::[]
// Aliases:
:conum-guard-yaml: #
ifndef::icons[:conum-guard-yaml: # #]
+ifdef::backend-pdf[:conum-guard-yaml: # #]
////
Topics remaining to document:
@@ -264,7 +265,7 @@ Like programming languages, multiple and divide take precedence over add and sub
The following table lists the supported operations and the corresponding operator for each.
-[%header%autowidth]
+[width=25%]
|===
|Operation |Operator
@@ -330,7 +331,7 @@ You can let the theme do this conversion for you automatically by adding a unit
The following units are supported:
-[%header%autowidth]
+[width=25%]
|===
|Unit |Suffix
@@ -419,17 +420,16 @@ Regardless, we recommend that you always use either a leading `+#+` or surroundi
The following are all equivalent values for the color red:
-[%autowidth,cols=4]
+[cols="8*m"]
|===
-|#f00
-|'f00'
|#ff0000
+|#FF0000
|'ff0000'
-
+|'FF0000'
+|#f00
|#F00
+|'f00'
|'F00'
-|#FF0000
-|'FF0000'
|===
Here's how a hex color value appears in the theme file:
@@ -541,7 +541,7 @@ There's nothing Asciidoctor can do to convince PDF to work without the right fon
The names of the built-in fonts (for general-purpose text) are as follows:
-[%header%autowidth]
+[width=33.33%]
|===
|Font Name |Font Family
@@ -788,6 +788,16 @@ See <
> for details.
|page:
margin: [0.5in, 0.67in, 1in, 0.67in]
+|margin_inner^[3]^
+|<>
+|page:
+ margin_inner: 0.75in
+
+|margin_outer^[3]^
+|<>
+|page:
+ margin_outer: 0.59in
+
|size
|/~https://github.com/prawnpdf/pdf-core/blob/0.6.0/lib/pdf/core/page_geometry.rb#L16-L68[Named size^] {vbar} <> +
(default: A4)
@@ -800,6 +810,8 @@ This limitation is due to a bug in Prawn 1.3.1.
The limitation will remain until AsciidoctorJ PDF upgrades to Prawn 2.x (an upgrade that is waiting on AsciidoctorJ to migrate to JRuby 9000).
For more details, see http://discuss.asciidoctor.org/Asciidoctor-YAML-style-file-for-PDF-and-maven-td3849.html[this thread].
. Target may be an absolute path or a path relative to the value of the `pdf-stylesdir` attribute.
+. The margins for `recto` (right-hand, odd-numbered) and `verso` (left-hand, even-numbered) pages are calculated automatically from the margin_inner and margin_outer values.
+These margins and used when the value `prepress` is assigned to the `media` document attribute.
=== Base
@@ -1331,7 +1343,7 @@ The keys in this category control the spacing around block elements when a more
Block styles are applied to the following block types:
-[cols="1a,1a,1a", grid=none, frame=none]
+[cols="3*a",grid=none,frame=none]
|===
|
* admonition
@@ -2350,7 +2362,7 @@ The keys in this category control the arrangement and style of running header an
|===
. The background color spans the width of the page, as does the border when a background color is specified.
-. `` can be `recto` (right-hand, odd pages) or `verso` (left-hand, even pages).
+. `` can be `recto` (right-hand, odd-numbered pages) or `verso` (left-hand, even-numbered pages).
. `` can be `left`, `center` or `right`.
IMPORTANT: You must define a height for the running header or footer, respectively, or it will not be shown.
@@ -2517,12 +2529,12 @@ The only thing you need to add to an existing build is the attributes mentioned
There are various settings in the theme you control using document attributes.
These settings override equivalent keys defined in the theme file, where applicable.
-[cols="2,3,6m"]
+[cols="2,3,6l"]
|===
|Attribute |Value Type |Example
|autofit-option
-|flag (default: _not set_)
+|flag (default: _off_)
|:autofit-option:
|chapter-label
@@ -2533,16 +2545,20 @@ These settings override equivalent keys defined in the theme file, where applica
|inline image macro (target is relative to `imagesdir`; can be image or PDF file)
|+:front-cover-image: image:front-cover.pdf[]+
+|media
+|screen {vbar} print {vbar} prepress
+|:media: prepress
+
|pagenums^[2]^
-|flag (default: _set_)
+|flag (default: _on_)
|:pagenums:
//|pdf-page-layout
-//|portrait, landscape
+//|portrait {vbar} landscape
//|:pdf-page-layout: landscape
|pdf-page-size
-|/~https://github.com/prawnpdf/pdf-core/blob/0.6.0/lib/pdf/core/page_geometry.rb#L16-L68[named size^], <> (width x height)
+|/~https://github.com/prawnpdf/pdf-core/blob/0.6.0/lib/pdf/core/page_geometry.rb#L16-L68[Named size^] {vbar} <>
|:pdf-page-size: 6in x 9in
|title-logo-image
@@ -2554,6 +2570,54 @@ These settings override equivalent keys defined in the theme file, where applica
. Controls whether the `page-number` attribute is accessible to the running header and footer content specified in the theme file.
Use the `noheader` and `nofooter` attributes to disable the running header and footer, respectively, from the document.
+== Publishing Mode
+
+Asciidoctor PDF provides the following features to assist with publishing:
+
+* Double-sided (mirror) page margins
+* Automatic facing pages
+
+These features are activated when you set the `media` attribute to `prepress` in the header of your AsciiDoc document or from the CLI or API.
+The following sections describe the behaviors that this setting activates.
+
+=== Double-Sided Page Margins
+
+The page margins for the recto (right-hand, odd-numbered) and verso (left-hand, even-numbered) pages are automatically calculated by replacing the side page margins with the values of the `page_margin_inner` and `page_margin_outer` keys.
+
+For example, let's assume you've defined the following settings in your theme:
+
+[source,yaml]
+----
+page:
+ margin: [0.5in, 0.67in, 0.67in, 0.67in]
+ margin_inner: 0.75in
+ margin_outer: 0.59in
+----
+
+The page margins for the recto and verso pages will be resolved as follows:
+
+recto page margin:: [0.5in, *0.59in*, 0.67in, *0.75in*]
+verso page margin:: [0.5in, *0.75in*, 0.67in, *0.59in*]
+
+The page margins alternate between recto and verso.
+The first page in the document is a recto page.
+
+=== Automatic Facing Pages
+
+If a document uses the book doctype, a blank page will be inserted, if necessary, to ensure the following pages are recto-facing pages:
+
+* Title page
+* Table of contents
+* First page of body content
+
+Other facing pages may be added in the future.
+
+For documents that use the article doctype, Asciidoctor PDF incorrectly places the document title and table of contents on their own pages.
+This can result in the page numbering and the page facing to be out of sync.
+As a workaround, Asciidoctor PDF inserts a blank page, if necessary, to ensure the first page of body content is a recto-facing page.
+
+You can check on the status of this defect by following /~https://github.com/asciidoctor/asciidoctor-pdf/issues/95[issue #95].
+
////
== Resources for Extending Asciidoctor PDF
diff --git a/lib/asciidoctor-pdf/converter.rb b/lib/asciidoctor-pdf/converter.rb
index 529b1b109..bd168ed3a 100644
--- a/lib/asciidoctor-pdf/converter.rb
+++ b/lib/asciidoctor-pdf/converter.rb
@@ -41,6 +41,7 @@ class Converter < ::Prawn::Document
AlignmentNames = ['left', 'center', 'right']
AlignmentTable = { '<' => :left, '=' => :center, '>' => :right }
ColumnPlacements = [:left, :center, :right]
+ PageSides = [:recto, :verso]
LF = %(\n)
DoubleLF = %(\n\n)
TAB = %(\t)
@@ -133,8 +134,13 @@ def convert_document doc
end
#assign_missing_section_ids doc
- # NOTE the on_page_create callback is called within a float context
+ # NOTE on_page_create is called within a float context
+ # NOTE on_page_create is not called for imported pages, front and back cover pages, and other image pages
on_page_create do
+ # NOTE we assume here that physical page number reflects page side
+ if @media == 'prepress' && (next_page_margin = @page_margin_by_side[page_side]) != page_margin
+ set_page_margin next_page_margin
+ end
# TODO implement as a watermark (on top)
if @page_bg_image
# FIXME implement fitting and centering for SVG
@@ -151,19 +157,19 @@ def convert_document doc
# NOTE a new page will already be started if the cover image is a PDF
start_new_page unless page_is_empty?
- toc_start_page_num = page_number
num_toc_levels = (doc.attr 'toclevels', 2).to_i
if (include_toc = doc.attr? 'toc')
- toc_page_nums = ()
- dry_run do
- toc_page_nums = layout_toc doc, num_toc_levels, 1
- end
- # NOTE reserve pages for the toc
- toc_page_nums.each do
- start_new_page
- end
+ start_new_page if @ppbook && verso_page?
+ toc_page_nums = page_number
+ dry_run { toc_page_nums = layout_toc doc, num_toc_levels, toc_page_nums }
+ # NOTE reserve pages for the toc; leaves cursor on page after last page in toc
+ toc_page_nums.each { start_new_page }
end
+ # FIXME only apply to book doctype once title and toc are moved to start page when using article doctype
+ #start_new_page if @ppbook && verso_page?
+ start_new_page if @media == 'prepress' && verso_page?
+
num_front_matter_pages = page_number - 1
font @theme.base_font_family, size: @theme.base_font_size, style: @theme.base_font_style.to_sym
doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
@@ -174,11 +180,7 @@ def convert_document doc
# QUESTION should we delete page if document is empty? (leaving no pages?)
delete_page if page_is_empty? && page_count > 1
- toc_page_nums = if include_toc
- layout_toc doc, num_toc_levels, toc_start_page_num, num_front_matter_pages
- else
- (0..-1)
- end
+ toc_page_nums = include_toc ? (layout_toc doc, num_toc_levels, toc_page_nums.first, num_front_matter_pages) : []
if page_count > num_front_matter_pages
layout_running_content :header, doc, skip: num_front_matter_pages unless doc.noheader
@@ -201,23 +203,37 @@ def convert_document doc
# TODO only allow method to be called once (or we need a reset)
def init_pdf doc
- theme = ThemeLoader.load_theme((doc.attr 'pdf-style'), (doc.attr 'pdf-stylesdir'))
- @theme = theme
+ @theme = theme = ThemeLoader.load_theme((doc.attr 'pdf-style'), (doc.attr 'pdf-stylesdir'))
pdf_opts = build_pdf_options doc, theme
# QUESTION should page options be preserved (otherwise, not readily available)
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
::Prawn::Document.instance_method(:initialize).bind(self).call pdf_opts
+ @page_margin_by_side = { recto: page_margin, verso: page_margin }
+ if (@media = doc.attr 'media', 'screen') == 'prepress'
+ @ppbook = doc.doctype == 'book'
+ page_margin_recto = @page_margin_by_side[:recto]
+ if (page_margin_outer = theme.page_margin_outer)
+ page_margin_recto[1] = @page_margin_by_side[:verso][3] = page_margin_outer
+ end
+ if (page_margin_inner = theme.page_margin_inner)
+ page_margin_recto[3] = @page_margin_by_side[:verso][1] = page_margin_inner
+ end
+ # NOTE prepare scratch document to use page margin from recto side
+ set_page_margin page_margin_recto unless page_margin_recto == page_margin
+ else
+ @ppbook = false
+ end
# QUESTION should ThemeLoader register fonts?
register_fonts theme.font_catalog, (doc.attr 'scripts', 'latin'), (doc.attr 'pdf-fontsdir', ThemeLoader::FontsDir)
- @page_bg_image = nil
- if (bg_image = resolve_background_image doc, theme, 'page-background-image')
- @page_bg_image = (bg_image == 'none' ? nil : bg_image)
+ if (bg_image = resolve_background_image doc, theme, 'page-background-image') && bg_image != 'none'
+ @page_bg_image = bg_image
+ else
+ @page_bg_image = nil
end
@page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
@fallback_fonts = [*theme.font_fallbacks]
@font_color = theme.base_font_color
@text_transform = nil
- @stamps = {}
# NOTE we have to init pdfmarks here while we have a reference to the doc
@pdfmarks = (doc.attr? 'pdfmarks') ? (Pdfmarks.new doc) : nil
init_scratch_prototype
@@ -325,10 +341,10 @@ def convert_section sect, opts = {}
start_new_page unless at_page_top? || cursor > (height_of title) + @theme.heading_margin_top + @theme.heading_margin_bottom + (@theme.base_line_height_length * 1.5)
end
# QUESTION should we store pdf-page-start, pdf-anchor & pdf-destination in internal map?
- sect.set_attr 'pdf-page-start', (start_page_num = page_number)
+ sect.set_attr 'pdf-page-start', (start_pgnum = page_number)
# QUESTION should we just assign the section this generated id?
# NOTE section must have pdf-anchor in order to be listed in the TOC
- sect.set_attr 'pdf-anchor', (sect_anchor = derive_anchor_from_id sect.id, %(#{start_page_num}-#{y.ceil}))
+ sect.set_attr 'pdf-anchor', (sect_anchor = derive_anchor_from_id sect.id, %(#{start_pgnum}-#{y.ceil}))
add_dest_for_block sect, sect_anchor
if type == :part
layout_part_title sect, title, align: align
@@ -1438,7 +1454,7 @@ def convert_inline_anchor node
end
#attrs << %( title="#{node.attr 'title'}") if node.attr? 'title'
attrs << %( target="#{node.attr 'window'}") if node.attr? 'window', nil, false
- if (((doc = node.document).attr? 'media', 'print') || (doc.attr? 'show-link-uri')) && !(node.has_role? 'bare')
+ if (@media != 'screen' || (node.document.attr? 'show-link-uri')) && !(node.has_role? 'bare')
# TODO allow style of visible link to be controlled by theme
%(#{node.text} [#{target}])
else
@@ -1625,6 +1641,7 @@ def layout_title_page doc
end
# NOTE a new page will already be started if the cover image is a PDF
start_new_page unless page_is_empty?
+ start_new_page if @ppbook && verso_page?
@page_bg_image = prev_bg_image if bg_image
@page_bg_color = prev_bg_color if bg_color
@@ -1729,10 +1746,9 @@ def layout_cover_page face, doc
if (cover_image.include? ':') && cover_image =~ ImageAttributeValueRx
cover_image = resolve_image_path doc, $1
end
- # QUESTION should we go to page 1 when face == :front?
go_to_page page_count if face == :back
if cover_image.downcase.end_with? '.pdf'
- # NOTE import_page automatically advances to next page afterwards
+ # NOTE import_page automatically advances to next page afterwards (can we change this behavior?)
import_page cover_image, advance: face != :back
else
image_page cover_image, canvas: true
@@ -1847,6 +1863,7 @@ def layout_table_caption node, width, alignment = :left, side = :top
end
end
+ # NOTE num_front_matter_pages is not used during a dry run
def layout_toc doc, num_levels = 2, toc_page_number = 2, num_front_matter_pages = 0
go_to_page toc_page_number unless (page_number == toc_page_number) || scratch?
start_page_number = page_number
@@ -1887,17 +1904,17 @@ def layout_toc_level sections, num_levels, line_metrics, dot_width, num_front_ma
# TODO it would be convenient to have a cursor mark / placement utility that took page number into account
go_to_page start_page_number if start_page_number != end_page_number
move_cursor_to start_cursor
- sect_page_num = (sect.attr 'pdf-page-start') - num_front_matter_pages
+ sect_explicit_pgnum = (sect.attr 'pdf-page-start') - num_front_matter_pages
spacer_width = (width_of NoBreakSpace) * 0.75
# FIXME this calculation will be wrong if a style is set per level
- num_dots = ((bounds.width - (width_of %(#{sect_title}#{sect_page_num}), inline_format: true) - spacer_width) / dot_width).floor
+ num_dots = ((bounds.width - (width_of %(#{sect_title}#{sect_explicit_pgnum}), inline_format: true) - spacer_width) / dot_width).floor
num_dots = 0 if num_dots < 0
# FIXME dots don't line up if width of page numbers differ
typeset_formatted_text [
{ text: %(#{(@theme.toc_dot_leader_content || DotLeaderDefault) * num_dots}), color: toc_dot_color },
# FIXME this spacing doesn't always work out; should we use graphics instead?
{ text: NoBreakSpace, size: (@font_size * 0.5) },
- { text: sect_page_num.to_s, anchor: sect_anchor, color: @font_color }], line_metrics, align: :right
+ { text: sect_explicit_pgnum.to_s, anchor: sect_anchor, color: @font_color }], line_metrics, align: :right
go_to_page end_page_number if start_page_number != end_page_number
move_cursor_to end_cursor
end
@@ -1925,9 +1942,9 @@ def admonition_icon_data key
end
# TODO delegate to layout_page_header and layout_page_footer per page
- def layout_running_content position, doc, opts = {}
+ def layout_running_content periphery, doc, opts = {}
# QUESTION should we short-circuit if setting not specified and if so, which setting?
- return unless (position == :header && @theme.header_height) || (position == :footer && @theme.footer_height)
+ return unless (periphery == :header && @theme.header_height) || (periphery == :footer && @theme.footer_height)
skip = opts[:skip] || 1
start = skip + 1
num_pages = page_count - skip
@@ -1972,10 +1989,10 @@ def layout_running_content position, doc, opts = {}
doc.set_attr 'page-count', num_pages
# TODO move this to a method so it can be reused; cache results
- content_dict = [:recto, :verso].inject({}) do |acc, side|
+ content_dict = PageSides.inject({}) do |acc, side|
side_content = {}
ColumnPlacements.each do |placement|
- if (val = @theme[%(#{position}_#{side}_#{placement}_content)])
+ if (val = @theme[%(#{periphery}_#{side}_#{placement}_content)])
# TODO support image URL (using resolve_image_path)
if (val.include? ':') && val =~ ImageAttributeValueRx &&
::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir')))
@@ -1992,7 +2009,7 @@ def layout_running_content position, doc, opts = {}
end
end
# NOTE set fallbacks if not explicitly disabled
- if side_content.empty? && position == :footer && @theme[%(footer_#{side}_content)] != 'none'
+ if side_content.empty? && periphery == :footer && @theme[%(footer_#{side}_content)] != 'none'
side_content = { side == :recto ? :right : :left => '{page-number}' }
end
@@ -2000,14 +2017,12 @@ def layout_running_content position, doc, opts = {}
acc
end
- if position == :header
+ if periphery == :header
trim_line_metrics = calc_line_metrics(@theme.header_line_height || @theme.base_line_height)
trim_top = page_height
# NOTE height is required atm
trim_height = @theme.header_height || page_margin_top
trim_padding = @theme.header_padding || [0, 0, 0, 0]
- trim_left = page_margin_left
- trim_width = page_width - trim_left - page_margin_right
trim_bg_color = resolve_theme_color :header_background_color
trim_border_width = @theme.header_border_width || @theme.base_border_width
trim_border_style = (@theme.header_border_style || :solid).to_sym
@@ -2019,8 +2034,6 @@ def layout_running_content position, doc, opts = {}
# NOTE height is required atm
trim_top = trim_height = @theme.footer_height || page_margin_bottom
trim_padding = @theme.footer_padding || [0, 0, 0, 0]
- trim_left = page_margin_left
- trim_width = page_width - trim_left - page_margin_right
trim_bg_color = resolve_theme_color :footer_background_color
trim_border_width = @theme.footer_border_width || @theme.base_border_width
trim_border_style = (@theme.footer_border_style || :solid).to_sym
@@ -2029,10 +2042,27 @@ def layout_running_content position, doc, opts = {}
trim_img_valign = @theme.footer_image_vertical_align
end
- trim_stamp = position.to_s
- trim_content_left = trim_left + trim_padding[3]
+ trim_stamp_name = {
+ recto: %(#{periphery}_recto),
+ verso: %(#{periphery}_verso)
+ }
+ trim_left = {
+ recto: @page_margin_by_side[:recto][3],
+ verso: @page_margin_by_side[:verso][3]
+ }
+ trim_width = {
+ recto: page_width - trim_left[:recto] - @page_margin_by_side[:recto][1],
+ verso: page_width - trim_left[:verso] - @page_margin_by_side[:verso][1]
+ }
+ trim_content_left = {
+ recto: trim_left[:recto] + trim_padding[3],
+ verso: trim_left[:verso] + trim_padding[3]
+ }
+ trim_content_width = {
+ recto: trim_width[:recto] - trim_padding[3] - trim_padding[1],
+ verso: trim_width[:verso] - trim_padding[3] - trim_padding[1]
+ }
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top - trim_line_metrics.padding_bottom
- trim_content_width = trim_width - trim_padding[3] - trim_padding[1]
trim_border_color = nil if trim_border_width == 0
trim_valign = :center if trim_valign == :middle
case trim_img_valign
@@ -2044,8 +2074,9 @@ def layout_running_content position, doc, opts = {}
trim_img_valign = trim_img_valign.to_sym
end
- colspec_dict = [:recto, :verso].inject({}) do |acc, side|
- if (custom_colspecs = @theme[%(#{position}_#{side}_columns)])
+ colspec_dict = PageSides.inject({}) do |acc, side|
+ side_trim_content_width = trim_content_width[side]
+ if (custom_colspecs = @theme[%(#{periphery}_#{side}_columns)])
colspecs = %w(<40% =20% >40%)
(custom_colspecs.tr ',', ' ').split[0..2].each_with_index {|c, idx| colspecs[idx] = c }
colspecs = { left: colspecs[0], center: colspecs[1], right: colspecs[2] }
@@ -2059,8 +2090,8 @@ def layout_running_content position, doc, opts = {}
pcwidth = spec.to_f
end
# QUESTION should we allow the columns to overlap (capping width at 100%)?
- if (w = trim_content_width * (pcwidth / 100.0)) + cml_width > trim_content_width
- w = trim_content_width - cml_width
+ if (w = side_trim_content_width * (pcwidth / 100.0)) + cml_width > side_trim_content_width
+ w = side_trim_content_width - cml_width
end
cml_width += w
[col, { align: alignment, width: w, x: 0 }]
@@ -2069,39 +2100,42 @@ def layout_running_content position, doc, opts = {}
acc[side] = side_colspecs
else
acc[side] = {
- left: { align: :left, width: trim_content_width, x: 0 },
- center: { align: :center, width: trim_content_width, x: 0 },
- right: { align: :right, width: trim_content_width, x: 0 }
+ left: { align: :left, width: side_trim_content_width, x: 0 },
+ center: { align: :center, width: side_trim_content_width, x: 0 },
+ right: { align: :right, width: side_trim_content_width, x: 0 }
}
end
acc
end
+ stamps = {}
if trim_bg_color || trim_border_color
# NOTE switch to first content page so stamp will get created properly (can't create on imported page)
prev_page_number = page_number
go_to_page start
- create_stamp trim_stamp do
- canvas do
- if trim_bg_color
- bounding_box [0, trim_top], width: bounds.width, height: trim_height do
- fill_bounds trim_bg_color
- if trim_border_color
+ PageSides.each do |side|
+ create_stamp trim_stamp_name[side] do
+ canvas do
+ if trim_bg_color
+ bounding_box [0, trim_top], width: bounds.width, height: trim_height do
+ fill_bounds trim_bg_color
+ if trim_border_color
+ # TODO stroke_horizontal_rule should support :at
+ move_down bounds.height if periphery == :header
+ stroke_horizontal_rule trim_border_color, line_width: trim_border_width, line_style: trim_border_style
+ end
+ end
+ else
+ bounding_box [trim_left[side], trim_top], width: trim_width[side], height: trim_height do
# TODO stroke_horizontal_rule should support :at
- move_down bounds.height if position == :header
+ move_down bounds.height if periphery == :header
stroke_horizontal_rule trim_border_color, line_width: trim_border_width, line_style: trim_border_style
end
end
- else
- bounding_box [trim_left, trim_top], width: trim_width, height: trim_height do
- # TODO stroke_horizontal_rule should support :at
- move_down bounds.height if position == :header
- stroke_horizontal_rule trim_border_color, line_width: trim_border_width, line_style: trim_border_style
- end
end
end
end
- @stamps[position] = true
+ stamps[periphery] = true
go_to_page prev_page_number
end
@@ -2110,30 +2144,32 @@ def layout_running_content position, doc, opts = {}
repeat (start..page_count), dynamic: true do
# NOTE don't write on pages which are imported / inserts (otherwise we can get a corrupt PDF)
next if page.imported_page?
- visual_pgnum = page_number - skip
+ explicit_pgnum = page_number - skip
+ # QUESTION should we respect physical page number or just look at the content page number?
+ side = page_side explicit_pgnum
# FIXME we need to have a content setting for chapter pages
- content_by_placement = content_dict[side = visual_pgnum.odd? ? :recto : :verso]
+ content_by_placement = content_dict[side]
colspec_by_placement = colspec_dict[side]
# TODO populate chapter-number
# TODO populate numbered and unnumbered chapter and section titles
# FIXME leave page-number attribute unset once we filter lines with unresolved attributes (see below)
- doc.set_attr 'page-number', (pagenums_enabled ? visual_pgnum : '')
- doc.set_attr 'chapter-title', (chapters_by_page[visual_pgnum] || '')
- doc.set_attr 'section-title', (sections_by_page[visual_pgnum] || '')
- doc.set_attr 'section-or-chapter-title', (sections_by_page[visual_pgnum] || chapters_by_page[visual_pgnum] || '')
+ doc.set_attr 'page-number', (pagenums_enabled ? explicit_pgnum : '')
+ doc.set_attr 'chapter-title', (chapters_by_page[explicit_pgnum] || '')
+ doc.set_attr 'section-title', (sections_by_page[explicit_pgnum] || '')
+ doc.set_attr 'section-or-chapter-title', (sections_by_page[explicit_pgnum] || chapters_by_page[explicit_pgnum] || '')
- stamp trim_stamp if @stamps[position]
+ stamp trim_stamp_name[side] if stamps[periphery]
- theme_font position do
+ theme_font periphery do
canvas do
- bounding_box [trim_content_left, trim_top], width: trim_content_width, height: trim_height do
+ bounding_box [trim_content_left[side], trim_top], width: trim_content_width[side], height: trim_height do
ColumnPlacements.each do |placement|
next unless (content = content_by_placement[placement])
next unless (colspec = colspec_by_placement[placement])[:width] > 0
# FIXME we need to have a content setting for chapter pages
case content
when ::Hash
- # NOTE image position respects padding; use negative image_vertical_align value to revert
+ # NOTE image vposition respects padding; use negative image_vertical_align value to revert
trim_v_padding = trim_padding[0] + trim_padding[2]
# NOTE float ensures cursor position is restored and returns us to current page if we overrun
float do
@@ -2146,7 +2182,7 @@ def layout_running_content position, doc, opts = {}
end
when ::String
if content == '{page-number}'
- content = pagenums_enabled ? visual_pgnum.to_s : nil
+ content = pagenums_enabled ? explicit_pgnum.to_s : nil
else
# FIXME get apply_subs to handle drop-line w/o a warning
doc.set_attr 'attribute-missing', 'skip' unless attribute_missing_doc == 'skip'
@@ -2156,7 +2192,7 @@ def layout_running_content position, doc, opts = {}
end
doc.set_attr 'attribute-missing', attribute_missing_doc unless attribute_missing_doc == 'skip'
end
- theme_font %(#{position}_#{side}_#{placement}) do
+ theme_font %(#{periphery}_#{side}_#{placement}) do
formatted_text_box parse_text(content, color: @font_color, inline_format: [normalize: true]),
at: [colspec[:x], trim_content_height + trim_padding[2] + trim_line_metrics.padding_bottom],
width: colspec[:width],
@@ -2176,44 +2212,26 @@ def layout_running_content position, doc, opts = {}
nil
end
- # FIXME we are assuming we always have exactly one title page
- def add_outline doc, num_levels = 2, toc_page_nums = (0..-1), num_front_matter_pages = 0
+ def add_outline doc, num_levels = 2, toc_page_nums = [], num_front_matter_pages = 0
front_matter_counter = RomanNumeral.new 0, :lower
-
page_num_labels = {}
- # FIXME account for cover page
- # cover page (i)
- #front_matter_counter.next!
-
- # title page (i)
- # TODO same conditional logic as in layout_title_page; consolidate
- if doc.header? && !doc.notitle
- page_num_labels[0] = { P: ::PDF::Core::LiteralString.new(front_matter_counter.next!.to_s) }
- end
-
- # toc pages (ii..?)
- toc_page_nums.each do
+ num_front_matter_pages.times do
page_num_labels[front_matter_counter.to_i] = { P: ::PDF::Core::LiteralString.new(front_matter_counter.next!.to_s) }
end
- # credits page
- #page_num_labels[front_matter_counter.to_i] = { P: ::PDF::Core::LiteralString.new(front_matter_counter.next!.to_s) }
-
- # number of front matter pages aside from the document title to skip in page number index
- numbering_offset = front_matter_counter.to_i - 1
+ # placeholder for first page of content, in case it's not the destination of an outline entry
+ page_num_labels[front_matter_counter.to_i] = { P: ::PDF::Core::LiteralString.new('1') }
outline.define do
# FIXME use sanitize: :plain_text once available
if (doctitle = document.sanitize(doc.doctitle use_fallback: true))
+ # FIXME link to title page if there's a cover page (skip cover page and ensuing blank page)
page title: doctitle, destination: (document.dest_top 1)
end
- if doc.attr? 'toc'
- page title: (doc.attr 'toc-title'), destination: (document.dest_top toc_page_nums.first)
- end
- #page title: 'Credits', destination: (document.dest_top toc_page_nums.first + 1)
+ page title: (doc.attr 'toc-title'), destination: (document.dest_top toc_page_nums.first) if toc_page_nums.first
# QUESTION any way to get add_outline_level to invoke in the context of the outline?
- document.add_outline_level self, doc.sections, num_levels, page_num_labels, numbering_offset, num_front_matter_pages
+ document.add_outline_level self, doc.sections, num_levels, page_num_labels, num_front_matter_pages
end
catalog.data[:PageLabels] = state.store.ref Nums: page_num_labels.flatten
@@ -2222,17 +2240,17 @@ def add_outline doc, num_levels = 2, toc_page_nums = (0..-1), num_front_matter_p
end
# TODO only nest inside root node if doctype=article
- def add_outline_level outline, sections, num_levels, page_num_labels, numbering_offset, num_front_matter_pages
+ def add_outline_level outline, sections, num_levels, page_num_labels, num_front_matter_pages
sections.each do |sect|
sect_title = sanitize sect.numbered_title formal: true
sect_destination = sect.attr 'pdf-destination'
- sect_page_num = (sect.attr 'pdf-page-start') - num_front_matter_pages
- page_num_labels[sect_page_num + numbering_offset] = { P: ::PDF::Core::LiteralString.new(sect_page_num.to_s) }
+ sect_explicit_pgnum = (sect_pgnum = sect.attr 'pdf-page-start') - num_front_matter_pages
+ page_num_labels[sect_pgnum - 1] = { P: ::PDF::Core::LiteralString.new(sect_explicit_pgnum.to_s) }
if (subsections = sect.sections).empty? || sect.level == num_levels
outline.page title: sect_title, destination: sect_destination
elsif sect.level < num_levels + 1
outline.section sect_title, { destination: sect_destination } do
- add_outline_level outline, subsections, num_levels, page_num_labels, numbering_offset, num_front_matter_pages
+ add_outline_level outline, subsections, num_levels, page_num_labels, num_front_matter_pages
end
end
end
@@ -2666,7 +2684,7 @@ def init_scratch_prototype
# IMPORTANT don't set font before using Marshal, it causes serialization to fail
@prototype = ::Marshal.load ::Marshal.dump self
@prototype.state.store.info.data[:Scratch] = true
- # we're now starting a new page each time, so no need to do it here
+ # NOTE we're now starting a new page each time, so no need to do it here
#@prototype.start_new_page if @prototype.page_number == 0
end
diff --git a/lib/asciidoctor-pdf/prawn_ext/extensions.rb b/lib/asciidoctor-pdf/prawn_ext/extensions.rb
index 0197a7492..4a3c210d9 100644
--- a/lib/asciidoctor-pdf/prawn_ext/extensions.rb
+++ b/lib/asciidoctor-pdf/prawn_ext/extensions.rb
@@ -11,6 +11,7 @@ module Extensions
IconSets = ['fa', 'fi', 'octicon', 'pf'].to_set
MeasurementValueRx = /(\d+|\d*\.\d+)(in|mm|cm|px|pt)?$/
+ InitialPageContent = %(q\n)
# - :height is the height of a line
# - :leading is spacing between adjacent lines
@@ -53,6 +54,20 @@ def effective_page_height
reference_bounds.height
end
+ # Set the margins for the current page.
+ #
+ def set_page_margin margin
+ # FIXME is there a cleaner way to set margins? does it make sense to override create_new_page?
+ apply_margin_options margin: margin
+ generate_margin_box
+ end
+
+ # Returns the margins for the current page as a 4 element array (top, right, bottom, left)
+ #
+ def page_margin
+ [page.margins[:top], page.margins[:right], page.margins[:bottom], page.margins[:left]]
+ end
+
# Returns the width of the left margin for the current page
#
def page_margin_left
@@ -93,20 +108,38 @@ def bounds_margin_right
page.dimensions[2] - bounds.absolute_right
end
- # Returns whether the cursor is at the top of the page (i.e., margin box)
+ # Returns the side the current page is facing, :recto or :verso.
+ #
+ def page_side pgnum = nil
+ (recto_page? pgnum) ? :recto : :verso
+ end
+
+ # Returns whether the page is a recto page.
+ #
+ def recto_page? pgnum = nil
+ (pgnum || page_number).odd?
+ end
+
+ # Returns whether the page is a verso page.
+ #
+ def verso_page? pgnum = nil
+ (pgnum || page_number).even?
+ end
+
+ # Returns whether the cursor is at the top of the page (i.e., margin box).
#
def at_page_top?
@y == @margin_box.absolute_top
end
- # Returns whether the current page is empty (no content is written).
- # If at least one page has not yet been created, returns false.
+ # Returns whether the current page is empty (i.e., no content has been written).
+ # Returns false if a page has not yet been created.
#
def empty_page?
# if we are at the page top, assume we didn't write anything to the page
#at_page_top?
- # ...or use low-level check (initial value is "q\n")
- (page.content.stream || []).length <= 2 && page_number > 0
+ # ...or use more robust, low-level check (initial value of content is "q\n")
+ page_number > 0 && page.content.stream.filtered_stream == InitialPageContent
end
alias :page_is_empty? :empty_page?
@@ -799,7 +832,7 @@ def keep_together_if verdict, &block
=begin
def run_with_trial &block
available_space = cursor
- whole_pages, remainder = dry_run(&block)
+ total_height, whole_pages, remainder = dry_run(&block)
if whole_pages > 0 || remainder > available_space
started_new_page = true
else
diff --git a/lib/asciidoctor-pdf/theme_loader.rb b/lib/asciidoctor-pdf/theme_loader.rb
index 7a412c6ee..91b16916d 100644
--- a/lib/asciidoctor-pdf/theme_loader.rb
+++ b/lib/asciidoctor-pdf/theme_loader.rb
@@ -81,10 +81,10 @@ def load hash, theme_data = nil
theme_data ||= ::OpenStruct.new
hash.inject(theme_data) {|data, (key, val)| process_entry key, val, data }
# NOTE remap legacy running content keys (e.g., header_recto_content_left => header_recto_left_content)
- %w(header_recto header_verso footer_recto footer_verso).each do |position_face|
+ %w(header_recto header_verso footer_recto footer_verso).each do |periphery_face|
%w(left center right).each do |align|
- if (val = theme_data.delete %(#{position_face}_content_#{align}))
- theme_data[%(#{position_face}_#{align}_content)] = val
+ if (val = theme_data.delete %(#{periphery_face}_content_#{align}))
+ theme_data[%(#{periphery_face}_#{align}_content)] = val
end
end
end