04. Creating Custom Designs

If you’re a web designer and need a unique design, creating a custom design is a good idea.

You’ll probably want to create a plugin that initializes the design, and contains the design’s HTML, CSS, Javascript and PHP files.

Sample Easy Design

To get you started, see our Sample Easy Design plugin on GitHub. Download it or check it out into your site’s wp-content/plugins directory and activate it. It will add a new digital PDF design called “Easy Academic Paper”.

On a project’s “Choose Digital PDF Design” step, with the plugin “Print My Blog – Sample Easy Design” active, there is now an extra option of “Easy Academic Paper”.

This plugin just does the bare minimum needed for a custom design:

The “Sample Easy Design”‘s files
  • declares itself to be a plugin, register a new design template and a design (in pmb-design-easy.php)
  • adds the design’s CSS (in design/assets/style.css) and Javascript (in design/assets/script.js) to be used on the Print Page
  • adds a template file for articles (in design/templates/article.php) and a reusable partial template file (in design/templates/partials/content.php)
  • a description of the new design (see design/description.php)
  • a PHP file that runs any extra PHP needed when generating the HTML Print Preview page (see design/functions.php)

pmb-design-easy.php

This file declares and registers stuff. Let’s step through it.

First, it registers this folder as a plugin, so WordPress knows to treat it as such.

<?php
/*
Plugin Name: Print My Blog - Sample Easy Design
Plugin URI: https://github.com/mnelson4/pmb-sample-design-easy
Description: A simple example design that does not support dividing projects into parts or front and back matter.
Author: Mike Nelson
Version: 1.0.0
Author URI: https://printmy.blog
*/
...

Next, there are some PHP use statements, saying this file will use some of Print My Blog’s autoloaded classes. Please note that all of PMB classes are autoloader and can be found in /src/ folder of PMB.

use PrintMyBlog\entities\DesignTemplate;
use PrintMyBlog\orm\entities\Design;
use Twine\forms\base\FormSection;
use Twine\forms\inputs\AdminFileUploaderInput;
use Twine\forms\inputs\ColorInput;
use Twine\forms\inputs\DatepickerInput;
use Twine\forms\inputs\TextAreaInput;
use Twine\forms\inputs\TextInput;
use Twine\forms\strategies\validation\TextValidation;

Define a few constants that will be useful

define('PMBE_MAIN_FILE', __FILE__);
define('PMBE_MAIN_DIR', __DIR__);

Setup an action callback and activation hook to register the design at the appropriate times

add_action( 'pmb_register_designs', 'pmbe_register_design', 1 );
register_activation_hook(PMBE_MAIN_FILE,'pmbe_activation');

The activation callback gets PMB to insert a design custom post type based on the new design.

/**
 * Called when this plugin is activated, and gets PMB to check this design exists in the database (and if not, adds it)
 */
function pmbe_activation(){
    pmbe_register_design();
    pmb_check_db();
}

Declares a function to register the design template and design

/**
 * Registers the design and design template. This should be done on every request so PMB knows they exist.
 */
function pmbe_register_design() {
    if(! function_exists('pmb_register_design')){
        return;
    }

Next, we register the design template using pmb_register_design_template (declared in the main PMB plugin in inc/integration_functions.php.

First we give the design template the slug easy_template, then provide a function that returns an array of data about the design template. Please see the documentation for PrintMyBlog/entities/DesignTemplate::construct() to learn about its stucture.

In this case, it’s registering a digital PDF design template with no front or back matter, and no parts. The design form just has two form inputs called “Internal Footnote Text” and “External Footnote Text”. The project metadata form has inputs for “institution”, “authors” and “date”.

pmb_register_design_template(
        'easy_template',
        function () {
            return [
                'title' => __('Easy Digital PDF'),
                'format' => 'digital_pdf',
                'dir' => PMBE_MAIN_DIR . '/design/',
                'default' => 'easy',
                'url' => plugins_url('design/', PMBE_MAIN_FILE),
                'docs' => '',
                'supports' => [
                ],
                'design_form_callback' => function () {
                    return (new FormSection([
                        'subsections' =>
                            [
                                'internal_footnote_text' => new TextInput([
                                    'html_label_text' => __('Internal Footnote Text', 'print-my-blog'),
                                    'html_help_text' => __('Text to use when replacing a hyperlink with a footnote. "%s" will be replaced with the page number.', 'print-my-blog'),
                                    'default' => __('See page %s.', 'print-my-blog'),
                                    'validation_strategies' => [
                                        new TextValidation(__('You must include "%s" in the footnote text so we know where to put the URL.', 'print-my-blog'), '~.*\%s.*~')
                                    ]
                                ]),
                                'footnote_text' => new TextInput([
                                    'html_label_text' => __('External Footnote Text', 'print-my-blog'),
                                    'html_help_text' => __('Text to use when replacing a hyperlink with a footnote. "%s" will be replaced with the URL', 'print-my-blog'),
                                    'default' => __('See %s.', 'print-my-blog'),
                                    'validation_strategies' => [
                                        new TextValidation(__('You must include "%s" in the footnote text so we know where to put the URL.', 'print-my-blog'), '~.*\%s.*~')
                                    ]
                                ])
                            ],
                    ]))->merge(pmb_generic_design_form());
                },
                'project_form_callback' => function (Design $design) {
                    return new FormSection([
                        'subsections' => [
                            'institution' => new TextInput(
                                [
                                    'html_label_text' => __('Issue', 'print-my-blog'),
                                    'html_help_text' => __('Text that appears at the top-right of the cover'),
                                ]
                            ),
                            'authors' => new TextAreaInput(
                                [
                                    'html_label_text' => __('ByLine', 'print-my-blog'),
                                    'html_help_text' => __('Project Author(s)', 'print-my-blog'),
                                ]
                            ),
                            'date' => new DatepickerInput([
                                'html_label_text' => __('Date Issued', 'print-my-blog'),
                                'html_help_text' => __('Text that appears under the byline', 'print-my-blog'),
                            ]),
                        ]
                    ]);
                }
            ];
        }
    );

Now that we’ve registered a design template, we can register an instance of that template, a design. The design will be saved as a custom post type, with postmetas for its default values for the design and project forms.

For documentation on pmb_register_design, see the function in inc/integration_functions.php and

pmb_register_design(
        'easy_template',
    'easy',
        function (DesignTemplate $design_template) {
            $preview_folder_url = plugins_url('/design/assets/', PMBE_MAIN_FILE);
            return [
                'title' => __('Easy Academic Paper', 'print-my-blog'),
                'quick_description' => __('An easy design', 'print-my-blog'),
                'description' => pmb_get_contents(PMBE_MAIN_DIR . '/design/description.php'),
                'author' => [
                    'name' => 'Mike Nelson',
                    'url' => 'https://printmy.blog'
                ],
                'previews' => [
                    [
                        'url' => $preview_folder_url . '/preview1.jpg',
                        'desc' => __('Title page, showing the double-spaced text.', 'print-my-blog')
                    ],
                    [
                        'url' => $preview_folder_url . '/preview2.jpg',
                        'desc' => __('Main matter, showing smaller images and double-spaced text.', 'print-my-blog')
                    ]
                ],
                'design_defaults' => [
                    'custom_css' => ''
                ],
                'project_defaults' => [
                    'institution' => 'Print My Blog'
                ],
            ];
        }
    );

/design/assets/style.css

This is the CSS file used when creating the PMB Pro Print Page. Remember it can use any CSS supported by Prince. It is automatically enqueued by PMB.

See the inline documentation for explanations of what’s being done here.

/* Use the @media selector to make the CSS selectors more likely to take precedence over competing styles from plugins, 
WordPress core, and possibly the active theme. */
@media print, screen {

    /*
    See https://www.princexml.com/doc/paged/#named-pages for documentation on "page", but basically it says the pmb-main-matter
    div should start on a new page which we'll call "main" throughout the rest of the CSS file
    See https://www.princexml.com/doc/gen-content/#counters-and-numbering for "counter-reset" says page numbering should
    start at 1 on the first page of main matter.
     */
    .pmb-main-matter {
        display: block;
        page: main;
        counter-reset: page 1
    }

    /**
    Each main page should have its footnotes reset, and its footnotes should have a solid black line on top and a bit of space.
    The page number should go in the bottom-right corner.
     */
    @page main {
        counter-reset: footnote;

        @footnotes {
            border-top: solid black thin;
            padding-top: 8pt;
        }
        @bottom-right {
            content: counter(page)
        }
    }
    /**
    See https://www.princexml.com/doc/styling/#footnotes for documentation on footnotes
     */
    body{
        prince-footnote-policy: keep-with-line;
    }

    /* General styles*/
    body{
        font-family:Times;
    }

    /**
    Avoid pagebreaks inside and after any headings.
    See https://www.princexml.com/doc/13/paged/#page-breaks
     */
    h1,h2,h3,h4,h5,h6{
        font-family:Calibri;
        page-break-after:avoid;
        page-break-inside:avoid;
    }

    /**
    Turn any spans with the CSS class "pmb-footnote" into footnotes
     */
    .pmb-posts span.pmb-footnote{
        float:footnote;
        text-align:left;
        margin-left:0;
        font-weight:normal;
    }
}

/design/assets/script.js

This script file is automatically enqueued by PMB on the print page. It’s used mainly for extra DOM manipulation. You can also enqueue any other Javascript files you want (more on this in a moment). But this file is enqueued after assets/scripts/print-page-beautifier-functions.js and jQuery, so makes use of them.

jQuery(document).ready(function(){
    pmb_remove_unsupported_content();
    // Pretty up the page
    pmb_add_header_classes();
    pmb_default_align_center();
    pmb_fix_wp_videos();
    pmb_convert_youtube_videos_to_images();
    pmb_load_avada_lazy_images();
    pmb_reveal_dynamic_content();
    pmb_replace_internal_links_with_page_refs_and_footnotes('footnote', 'footnote', pmb_design_options['external_footnote_text'], pmb_design_options['internal_footnote_text']);
    new PmbToc();
    jQuery(document).trigger('pmb_wrap_up');
});

// wait until the images are loaded to try to resize them.
jQuery(window).on("load", function() {
    pmb_resize_images(400);
    jQuery(document).trigger('pmb_wrap_up');
});

design/templates/article.php

This is a template file that instructs PMB how to convert a post into the HTML on the PMB Pro Print Page. It makes use of the WordPress template tags and PMB’s template functions in /inc/template_functions.php.

As you can see it’s creating HTML following PMB’s normal HTML structure. It just adds the project’s title and then includes templates/partials/content.php to show the post’s content.

<?php
/**
 * @var \PrintMyBlog\orm\entities\Project $pmb_project
 * @var PrintMyBlog\orm\entities\Design $pmb_design
 */
?>
<div <?php pmb_section_wrapper_class();?> <?php pmb_section_wrapper_id();?>>
    <article <?php pmb_section_class(); ?> <?php pmb_section_id(); ?>>
        <header class="entry-header has-text-align-center">

            <div class="entry-header-inner section-inner medium">
				<?php pmb_the_title();?>
            </div><!-- .entry-header-inner -->
        </header><!-- .entry-header -->
		<?php pmb_include_design_template( 'partials/content' ); ?>
    </article>
<?php

/design/description.php

This is just static HTML that appears in the “Design Details” modal window on the “Choose Design” step.

/design/functions.php

This is a PHP file that’s executed only when generating a project from the design. It’s a good place to add filters and actions, and especially to enqueue other stylesheets or scripts.

This file is passing some dynamic CSS using wp_add_inline_style, and some variables to the Javascript using wp_localize_script (most notably, the user’s choice for “Internal Hyperlink Text” and “External Hyperlink Text” set on the design customization step.

<?php
// Add filters, action callback, and functions you want to use in your design.
// Note that this file only gets included when generating a new project, not on every pageload.
add_action(
	'pmb_pdf_generation_start',
	function(\PrintMyBlog\entities\ProjectGeneration $project_generation, \PrintMyBlog\orm\entities\Design $design){
        global $pmb_design;
        $pmb_design = $design;
        add_action('wp_enqueue_scripts', 'pmb_enqueue_easy_script', 1001);
	},
	10,
	2
);

function pmb_enqueue_easy_script(){
    global $pmb_design;
    $css = pmb_design_styles($pmb_design);

    // dynamic CSS can go here
    wp_add_inline_style(
        'pmb_print_common',
        $css
    );

    // Pass some data to the Javascript
    wp_localize_script(
        'pmb-design',
        'pmb_design_options',
        [
            'internal_footnote_text' => $pmb_design->getSetting('internal_footnote_text'),
            'external_footnote_text' => $pmb_design->getSetting('footnote_text')
        ]
    );
}

Sample Complex Design

This design is more complex because it supports front and back matter, and organizing articles into parts, parts into volumes, and volumes into anthologies.

It’s also a GitHub repository you can download or checkout, place in your site’s wp-content/plugins folder, and activate.

The main difference is that its main file, pmb-design-complex.php declares it supports parts, volumes, and anthologies, each of which has a corresponding template file in design/templates/. Getting familiar with the standard HTML structure will help to understand the structure of each (there’s a wrapping div which isn’t closed, an article element, which has the post’s title and probably content, and then you let PMB take care of adding sub-sections.)

It’s likely that you might want to also register some custom section templates to use in a more advanced design like this (eg you could want a “fullpage cover photo” design) so you may want to combine this with elements from creating a custom section template.

Further Reading

To learn more, it will also help to study the designs included with PMB.

The design templates are all registered in PMB in /src/PrintMyBlog/domain/DefaultDesignTemplates.php and the design instances are registered in /src/PrintMyBlog/domain/DefaultDesigns.php.

Each design’s files are in PMB in /designs folder.

If you have questions (or really, if you just read this far!) please get in touch and let me know what you’re wanting to accomplish and if there are any questions. Good luck!