Thursday, July 20, 2023

Formatting for paper

Having decided on our approach, we need to implement it.

We will repeat our earlier approach of dividing the work into "static" and "measuring" stylesheets. So, we start by including a new stylesheet, printing.css, which we include with the media type print.

First off, we want to turn off all the irrelevant features, so we set the .controls div to have display: none and the feedback div to have no border.

To test this, it is possible to use the Chrome Developer Tools to select a "render emulation mode" of "print". Exactly how varies between versions; right now (v115) there is a kabob menu on the developer tools which has "More tools" one of which is "Rendering". In the pane that opens there is an item "Emulate CSS media type" for which one of the options is "print". By selecting that, you can see the effects of your print stylesheets directly in the browser window. You can also have the same page loaded in two separate windows, so that you can see the effects of screen and print media side by side.

So far, so good. Now onto the hard part.

We have defined all our measurements for the screen in a stylesheet. We want to keep that, so we need to add one in parallel that defines the measurements for printing on our chosen paper size (my plan had been to have one for each paper size and have the right one be chosen during printing, but this way is less work for me). So, first, we can go back and make the measuring sheet we have already created "screen only". (Unlike the base spreadsheet, there is nothing we want to reuse from this - it is all measurements that will be different between the two media). This just involves adding an object {media: "screen"} to the constructor of the CSSStyleSheet. Doing this reduces our print window to gibberish again as all the measurements have disappeared.

We next create a second sheet with the print media type, and ensure that both of them are adopted by the document.

This is all checked in as PLANNING_CALENDAR_MINIMAL_PRINT_STYLESHEET.

Now we need to do a refactoring. The method fitToPageSize is called from redraw whenever anything changes. This includes the start and end dates, the first day of the week, the size of the screen and the paper options. Some of these affect the screen, some the printed page, some both, some neither. But rather than figure that out, we are just going to relayout both screen and printed page every time. And it is basically exactly the same logic, just with different options. So my strategy is to tease this apart and to have this method call a new pageLayout method that does the laying out given the number of rows, the stylesheet to populate, and a set of options about size and the like. For now, I'm not going to do anything with the new print stylesheet, just refactor the code we have currently.

This involves removing a couple of the globals (borderX, borderY), moving the call to calculateSizeOfFeedbackDiv to the invocation of the new pageLayout method, and enhancing the result from calculateSizeOfFeedbackDiv to include the size of the border and the measurement unit (the screen will use px and the paper either mm or in).

That is then tagged as PLANNING_CALENDAR_EXTRACT_PAGELAYOUT.

This, of course, has done nothing for printing, except that now we can write one line that does handle the print case:
  pageLayout(printSheet, rows, calculatePaperSize());
This does depend on the calculatePaperSize method, however, but for now we are just going to hack that in:
function calculatePaperSize() {
  var borderX = 1, borderY = 1;
  return { x : 210, y : 297, unitIn: "mm", borderX, borderY };
}
And we can tag that as PLANNING_CALENDAR_PRINT_LAYOUT.

Of course, nothing is ever that simple, and we soon find that although this "fits" on a single piece of paper, it takes three. There is a very simple reason for this: paper has margins and we are using the whole of the piece of paper and thus overwriting the margins. I don't know how to find out what margins it is allowing than I do what sized paper it is using, but I do know how to overwrite the margins: an @page directive allows us to set the margins. We can choose any margin we want and then do our calculations based on having that much less space. (There is also an issue of duplicate margins, which can be addressed by recognizing the top and bottom rows as special, along with the left and right columns, and ensuring that the appropriate margin setting is 0 in this case; I can't be bothered right now, although I may come back to it; pull requests welcome).

OK, checked in and tagged as PLANNING_CALENDAR_PRINT_MARGINS.

So, finally for the basic calendar, we need to handle the various page sizes (and landscape) that we offered in the dropdowns. This is just some simple comparison of the currently selected values and returning the right hardcoded objects in calculatePaperSize. The landscape option is handled by taking the hardcoded object and then exchanging the X and Y values.

So that, apart from a ludicrous amount of testing, is that for the basic calendar. And is checked in as PLANNING_CALENDAR_BASIC_COMPLETE.

So what is left? Well, there are thousands of features that could be added, but I'm going to add three:
  • Annotations of the current month/year and other styling (such as weekends);
  • Ability to automatically populate with ICS calendars loaded over the web;
  • The ability to define a calendar template for easier sharing.
I may add other features over time, including the ability to split a calendar over multiple pages, but that is more likely to go straight into the live version than be described here. And, as I say, pull requests are always welcome.

No comments:

Post a Comment