2 Part of Objavi2, which makes pdf versions of FLOSSManuals books.
3 This is a QSA script library for pdfedit which provides various page
6 Copyright (C) 2009 Douglas Bagnall
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * Shift pages alternately left or right, and add page numbers in the
27 * This is a QSA script for pdfedit. QSA is a (deprecated) dialect of
28 * ecmascript used for scripting QT applications like pdfedit.
32 const DEFAULT_OFFSET = 25;
33 /* FLOSS Manuals default book size at lulu: 'Comicbook', 6.625" x 10.25" */
34 const COMIC_WIDTH = (6.625 * 72);
35 const COMIC_HEIGHT = (10.25 * 72);
37 const PAGE_NUMBER_SIZE = 11;
39 const DEFAULT_DIR = 'LTR';
41 const DEFAULT_NUMBER_STYLE = 'latin';
42 const DEFAULT_ENGINE = 'webkit';
44 function transform_page(page, offset){
45 page.setTransformMatrix([1, 0, 0, 1, offset, 0]);
48 function rotate_page180(page){
49 /* From the PDF reference:
51 * Rotations are produced by
52 * [cos(theta) sin(theta) −sin(theta) cos(theta) 0 0],
53 * which has the effect of rotating the coordinate
54 * system axes by an angle theta counterclockwise.
56 * but the rotation is about the 0,0 axis: ie the top left.
57 * So addin the width and height is necessary.
59 * There is *also* a Rotate key in the page or document dictionary
60 * that can be used to rotate in multiples of 90 degrees. But I did
61 * not find that first, and it fails mysteriously in pdfedit.
63 * var d = page.getDictionary();
64 * d.add('Rotate', createInt(180));
67 var box = page.mediabox();
68 //print("box is " + box);
71 var c = Math.cos(angle);
72 var s = Math.sin(angle);
73 page.setTransformMatrix([c, s, -s, c, box[2], box[3]]);
76 function adjust_for_direction(pdf, dir){
77 /* RTL book have gutter on the other side */
78 /*rotate the file if RTL*/
81 process_pdf(pdf, rotate_page180);
85 function prepend_content(page, content, prepended_op){
86 /* Webkit applies a transformation matrix so it can write the pdf
87 * using its native axes. That means, to use the default grid we
88 * need to either insert the page number before the webkit matrix,
89 * or apply an inverse. The inverse looks like:
91 * createOperator("cm", iprop_array('nnnnnn', 16.66667, 0, 0, -16.66667, -709.01015, 11344.83908));
93 * but it is simpler to jump in first.
95 * XXX warning: the order in which these things are applied can matter.
97 if (prepended_op == undefined)
100 var stream = page.getContentStream(0);
101 var iter = stream.getFirstOperator().iterator();
105 if (op.getName() == prepended_op){
110 } while (iter.next());
112 stream.insertOperator(op, content);
116 function grow_and_shift_page(page, data){
117 //get the box size in order to recentre (i.e. add half width & height delta)
118 var box = page.mediabox();
121 var w = box[2] - box[0];
122 var h = box[3] - box[1];
124 page.setMediabox(x, y, x + data.width, y + data.height);
126 var dx = data.offset + (width - w) * 0.5;
127 var dy = (height - h) * 0.5;
129 var cm = createOperator("cm", iprop_array('nnnnnn', 1, 0, 0, 1, dx, dy));
130 prepend_content(page, cm, 'q');
135 function shift_page_mediabox(page, data){
136 var box = page.mediabox();
139 var w = box[2] - box[0];
140 var h = box[3] - box[1];
142 if (data.width || data.height){
143 /*resize each page, so put the mediabox out and up by half of
145 XXX should the page really be centred vertically? */
146 x -= 0.5 * (data.width - w);
147 y -= 0.5 * (data.height - h);
150 //print("now x, y = " + x + ", " + y);
152 page.setMediabox(x - data.offset, y, x + w - data.offset, y + h);
155 function add_transformation(page, data){
156 var t = data.transform;
157 var cm = createOperator("cm", iprop_array('nnnnnn', t[0], t[1], t[2], t[3], t[4], t[5], t[6]));
163 * Helper functions for creating pdf operators. You can create an
164 * IPropertyArray or a PdfOperator like this:
166 * var array = iprop_array("Nnns", "Name", 1, 1.2, "string"); var rg =
167 * operator('rg', "nnn", 0, 1, 0);
169 * where the first argument to operator and the second to iprop_array
170 * is a template (ala Python's C interface) that determines the
171 * interpretation of subsequent parameters. Common keys are 'n' for
172 * (floaing point) number, 's' for string, 'N' for name, 'i' for
173 * integer. See the convertors object below.
180 //c: createCompositeOperator,
182 o: createEmptyOperator,
189 z: function(x){ return x; } //for pre-existing object, no processing.
192 function iprop_array(pattern){
193 //print("doing " + arguments);
194 var array = createIPropertyArray();
195 for (i = 1; i < arguments.length; i++){
196 var s = pattern.charAt(i - 1);
197 var arg = arguments[i];
198 var op = convertors[s](arg);
205 /* roman_number(number) -> lowercase roman number + approximate width */
206 function roman_number(number){
219 for (i = 0; i < data.length; i += 2){
220 var value = data[i + 1];
221 var letter = data[i];
222 while (number >= value){
236 for (i = 0; i < subs.length; i+= 2){
237 s = s.replace(subs[i], subs[i + 1]);
240 /* Try to take into account variable widths.
241 * XXX these numbers are made up, and font-specific.
254 for (i = 0; i < s.length; i++){
255 w += widths[s.charAt(i)];
265 /* change_number_charset(number, charset) -> unicode numeral + approx. width
266 farsi = '۰۱۲۳۴۵۶۷۸۹';
267 arabic = '٠١٢٣٤٥٦٧٨٩';
269 function change_number_charset(number, charset){
271 arabic: ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'],
272 farsi: ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']
274 if (charset == undefined)
276 var numerals = charsets[charset];
277 var west = number.toString();
280 for (i = 0; i < west.length; i++){
281 var c = west.charAt(i);
282 s += numerals[parseInt(c)];
284 //lazy guess at width
285 var w = 0.6 * s.length;
292 function latin_number(number){
293 var text = number.toString();
294 /* It would be nice to know the bounding box of the page number,
295 but it is not rendered during this process, so we have to guess.
296 All Helvetica numerals are the same width (approximately N shape)
297 so I'll assume 0.6ish.
301 width: text.length * 0.6
307 arabic: change_number_charset,
308 farsi: change_number_charset,
312 function add_page_number(page, number, dir, style, margin, bottom,
313 preceding_operators){
314 //print(' ' + number + ' ' + dir + ' ' + style);
315 if (! style in stringifiers || style == undefined){
318 var box = page.mediabox();
319 var h = PAGE_NUMBER_SIZE;
320 var n = stringifiers[style](number, style);
324 var y = box[1] + bottom;
325 var x = box[0] + margin;
327 if ((number & 1) == (dir != 'RTL')){
328 x = box[2] - margin - w;
331 var q = createCompositeOperator("q", "Q");
332 var BT = createCompositeOperator("BT", "ET");
334 /* it would be nice to use the book's font, but it seems not to
335 work for unknown reasons. */
336 page.addSystemType1Font("Helvetica");
337 var font = page.getFontId("Helvetica"); // probably 'PDFEDIT_F1'
339 var rg = createOperator("rg", iprop_array('nnn', 0, 0, 0));
340 var tf = createOperator("Tf", iprop_array('Nn', font, h));
341 var td = createOperator("Td", iprop_array('nn', x, y));
342 var tj = createOperator("Tj", iprop_array('s', text));
343 var et = createOperator("ET", iprop_array());
344 var end_q = createOperator("Q", iprop_array());
352 /*If given extra operators, push them in first */
353 if (0 && preceding_operators != undefined){
354 print('' + preceding_operators[0]);
356 for (i = 0; i < preceding_operators.length; i++){
357 q.pushBack(preceding_operators[i], q);
362 q.pushBack(end_q, BT);
364 var ops = createPdfOperatorStack();
366 page.appendContentStream(ops);
371 function number_pdf_pages(pdf, dir, number_style, start,
373 preceding_operators){
374 var pages = pdf.getPageCount();
377 print("numbers start at " + start + "; offset is " + offset + "sum = " + (start + offset));
378 start = parseInt(start) || 1;
380 /*count down (-start) pages before beginning */
385 /* start numbering at (start) */
388 for (i = start; i <= pages - offset; i++){
389 add_page_number(pdf.getPage(i + offset), i, dir, number_style,
390 margin, bottom, preceding_operators);
395 function process_pdf(pdf, func, data, skip_start, skip_end){
396 var pages = pdf.getPageCount();
398 if (! isNaN(skip_start)){
399 print ("skipping " + skip_start + "pages");
402 data[data.flip] = -data[data.flip];
403 print (data.flip + " is now " + data[data.flip]);
405 if (! isNaN(skip_end)){
408 for (; i <= pages; i++){
409 func(pdf.getPage(i), data);
410 if (data != undefined && data.flip)
411 data[data.flip] = -data[data.flip];
415 function even_pages(pdf){
416 /* if the pdf has an odd number of pages, cut one off the end.
417 * The pdf generator should have made an extra page in case this is necessary.
420 var pages = pdf.getPageCount();
422 pdf.removePage(pages); //one-based numbering
427 function save_text_index(pdf, filename){
428 //create an index file for finding chapter page numbers
429 var pages = pdf.getPageCount();
431 var outfile = new File(filename);
432 outfile.open(File.WriteOnly);
434 var write = function(x){
435 outfile.write(x + '\n');
438 for (var i = 1; i <= pages; i++){
439 write('\n-=-=- Magic Page Separating Line Not Found In Any Books -=-=-');
441 write((pdf.getPage(i).getText()));
451 function parse_options(parameters, options, convertors){
452 /* split parameters on the first '=' and return a mapping object.
453 * a mapping of default options can be passed in. */
454 if (options == undefined)
456 if (convertors == undefined)
459 print(parameters + '');
461 for (i = 0; i < parameters.length; i++){
462 var p = parameters[i];
463 if (p == 'h' || p == 'help')
464 commandline_help(options);
466 var split = p.indexOf('=');
467 var key = p.substring(0, split);
468 var value = p.substring(split + 1);
469 if (key in convertors)
470 options[key] = convertors[key](value);
472 options[key] = value;
477 function commandline_help(options){
478 print("options are:");
480 for (var o in options){
481 print(o + padding.substring(0, padding.length - o.length) + '[' + options[o] + ']');