Skip to content

Commit

Permalink
pango-markup: Add custom headers/footers
Browse files Browse the repository at this point in the history
- add header/footer properties
- render headers, footers in smaller text than the main copy
- support `%%` and `%p` replacements.
  • Loading branch information
cxw42 committed Sep 8, 2020
1 parent cc4f453 commit 5838247
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 39 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ Before submitting a PR, please run `make prep`. This will:
be left in the tree. To remove the generated C files,
`make maintainer-clean`.

### Design decisions

- Decisions about the exact appearance of an item should be made as late
as possible. For example, in the `pango-markup` writer (the default),
headers and footers are set in smaller type by the writer, not the upstream
code that feeds markup to the writer.

## Thanks

- <https://github.com/stefantalpalaru/vala-skeleton-autotools>
Expand Down
4 changes: 3 additions & 1 deletion src/core/writer.vala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ namespace My {
* @param sourcefn The filename of the source that @doc came from.
* This can be used, e.g., to resolve relative paths
* to images.
* @param template The template selected by the user, if any
*/
public abstract void write_document(string filename, Doc doc, string? sourcefn = null)
public abstract void write_document(string filename, Doc doc,
string? sourcefn = null)
throws FileError, My.Error;

/**
Expand Down
4 changes: 3 additions & 1 deletion src/pfft.vala
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,9 @@ namespace My {

retval.set_property(nv[0], val);
ldebugo(retval, "Set property %s from command line to %s",
nv[0], Gst.Value.serialize(val));
nv[0], val.type() == typeof(string) ? @"'$(val.get_string())'" :
Gst.Value.serialize(val));

} // foreach option

return retval;
Expand Down
2 changes: 1 addition & 1 deletion src/reader/md4c-reader.vala
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ namespace My
break;
case CODE: newnode = node_of_ty(SPAN_CODE); break;
case DEL: newnode = node_of_ty(SPAN_STRIKE); break;
case U: newnode = node_of_ty(SPAN_UNDERLINE); break;
case SpanType.U: newnode = node_of_ty(SPAN_UNDERLINE); break;
default:
printerr("Unsupported span type %s\n".printf(span_type.to_string()));
newnode = node_of_ty(SPAN_PLAIN);
Expand Down
3 changes: 2 additions & 1 deletion src/writer/dumper.vala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace My {
[Description(blurb = "Dump pfft's internal representation of the document (for debugging)")]
public bool meta { get; default = false; }

public void write_document(string filename, Doc doc, string? source_fn = null)
public void write_document(string filename, Doc doc,
string? source_fn = null)
throws FileError, My.Error
{
emit(filename, doc.as_string());
Expand Down
19 changes: 11 additions & 8 deletions src/writer/pango-blocks.vala
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ namespace My {

/**
* Render this shape at the current position.
* @param cr The Cairo context
* @param do_path See CairoShapeRendererFunc
*
* Leaves the current position at the right side of the rendering.
* This is because shapes are inline (span-like), so there is
* more text following the shape, in the general case.
*
* @param cr The Cairo context
* @param do_path See CairoShapeRendererFunc
*/
public abstract void render(Cairo.Context cr, bool do_path);

Expand All @@ -71,10 +72,10 @@ namespace My {
} // class Base

/**
* An image
* An image.
*
* This class is based on C code by Mike Birch, which code he kindly
* placed in the public domain. <https://immortalsofar.com/PangoDemo/>.
* placed in the public domain. [[https://immortalsofar.com/PangoDemo/]].
*/
public class Image : Base {
/**
Expand Down Expand Up @@ -208,13 +209,14 @@ namespace My {

/**
* Create an Image referencing an external image file.
*
* NOTE: at present, assumes that @href is a path from the
* location of the source file to the location of a PNG file.
*
* @param href Where the referenced image is
* @param doc_path Where the referencing file is.
* This is a string rather than a File so it
* can later be expanded to URLs.
*
* NOTE: at present, assumes that @href is a path from the
* location of the source file to the location of a PNG file.
*/
public Image.from_href(string href, string doc_path, int paddingP = -1)
{
Expand Down Expand Up @@ -486,10 +488,11 @@ namespace My {

/**
* Initialize shape_attrs.
* @param text The actual text in the layout --- NOT the markup
*
* Call only when the markup for the block has been finalized and
* all add_shape() calls have been made.
*
* @param text The actual text in the layout --- NOT the markup
*/
protected void fill_shape_attrs(string text)
{
Expand Down
156 changes: 129 additions & 27 deletions src/writer/pango-markup.vala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ namespace My {
/** The Pango layout for bullets and numbers */
Pango.Layout bullet_layout = null;

/** The Pango layout for the page numbers and headers */
/** The Pango layout for the */
Pango.Layout pageno_layout = null;

/** Current page */
Expand All @@ -108,6 +108,22 @@ namespace My {
[Description(nick = "Header margin (in.)", blurb = "Space between the top of the text block and the top of the header, in inches")]
public double headerskipI { get; set; default = 0.4; }

// Header parameters
[Description(nick = "Header markup, left", blurb = "Pango markup for the header, left side")]
public string headerl { get; set; default = ""; }
[Description(nick = "Header markup, center", blurb = "Pango markup for the header, middle")]
public string headerc { get; set; default = ""; }
[Description(nick = "Header markup, right", blurb = "Pango markup for the header, right side")]
public string headerr { get; set; default = ""; }

// Footer parameters
[Description(nick = "Footer markup, left", blurb = "Pango markup for the footer, left side")]
public string footerl { get; set; default = ""; }
[Description(nick = "Footer markup, center", blurb = "Pango markup for the footer, middle")]
public string footerc { get; set; default = "%p"; }
[Description(nick = "Footer markup, right", blurb = "Pango markup for the footer, right side")]
public string footerr { get; set; default = ""; }

/** Used in process_node_into() */
private Regex re_newline = null;

Expand All @@ -120,18 +136,23 @@ namespace My {
private Regex re_command = null;

/**
* Header markup
* Regex for placeholders in headers/footers.
*
* TODO handle headers/footers in a more general way
* Currently supported placeholders are:
* * `%p`: page number
* * `%%`: a literal percent sign
*/
private string header_markup = "";
private Regex re_hf_placeholder = null;

// Not a ctor since we create instances through g_object_new() --- see
// https://gitlab.gnome.org/GNOME/vala/-/issues/650
construct {
try {
re_newline = new Regex("\\R+");
re_command = new Regex("^pfft:\\s*(\\w+)");
re_hf_placeholder = new Regex("%(?<which>[p%])(?!\\w)");
// can't use \b in place of the negative lookahead because
// \b doesn't match between two non-word chars
} catch(RegexError e) { // LCOV_EXCL_START
lerroro(this, "Could not create required regexes --- I can't go on");
assert(false); // die horribly --- something is very wrong!
Expand All @@ -145,11 +166,11 @@ namespace My {
* @param sourcefn The filename of the source that @doc came from.
* TODO make paper size a parameter
*/
public void write_document(string filename, Doc doc, string? sourcefn = null) throws FileError, My.Error
public void write_document(string filename, Doc doc, string? sourcefn = null)
throws FileError, My.Error
{
source_fn = (sourcefn == null) ? "" : sourcefn;

int hsizeP = i2p(hsizeI);
int rightP = i2p(lmarginI+hsizeI);
int bottomP = i2p(tmarginI+vsizeI);

Expand All @@ -166,8 +187,6 @@ namespace My {
bullet_layout = Blocks.new_layout_12pt(cr);

pageno_layout = Blocks.new_layout_12pt(cr); // Layout for page numbers
pageno_layout.set_width(hsizeP);
pageno_layout.set_alignment(CENTER);

cr.move_to(i2c(lmarginI), i2c(tmarginI));
// over, down (respectively) from the UL corner
Expand Down Expand Up @@ -221,20 +240,10 @@ namespace My {
void eject_page()
{
linfoo(this, "Finalizing page %d", pageno);

// render the page header on the page we just finished
if(header_markup != "") {
pageno_layout.set_markup(header_markup, -1);
cr.move_to(i2c(lmarginI), i2c(tmarginI-headerskipI));
Pango.cairo_show_layout(cr, pageno_layout);
}

// render the page number on the page we just finished
pageno_layout.set_text(pageno.to_string(), -1);
cr.move_to(i2c(lmarginI), i2c(tmarginI+vsizeI+footerskipI));
Pango.cairo_show_layout(cr, pageno_layout);
render_headers_footers();
cr.show_page();

// Start the next page
++pageno;
cr.new_path();
cr.move_to(i2c(lmarginI), i2c(tmarginI));
Expand All @@ -246,6 +255,106 @@ namespace My {

} // eject_page()

/** render the page header(s)/footer(s) on the page we just finished */
private void render_headers_footers()
{
int hsizeP = i2p(hsizeI);

double headeryI = tmarginI-headerskipI;
render_one_hf("headerL", headerl, hsizeP, LEFT, lmarginI, headeryI);
render_one_hf("headerC", headerc, hsizeP, CENTER, lmarginI, headeryI);
render_one_hf("headerR", headerr, hsizeP, RIGHT, lmarginI, headeryI);

double footeryI = tmarginI+vsizeI+footerskipI;
render_one_hf("footerL", footerl, hsizeP, LEFT, lmarginI, footeryI);
render_one_hf("footerC", footerc, hsizeP, CENTER, lmarginI, footeryI);
render_one_hf("footerR", footerr, hsizeP, RIGHT, lmarginI, footeryI);
}

/** Replace "%p" and other placeholders in header/footer text */
private bool replace_hf_placeholders (string ident, MatchInfo match_info, StringBuilder result)
{
bool ok = false;
ltraceo(this, "HF %s: checking placeholders", ident);

do { // once
if(match_info.get_match_count() == 0) {
break;
}

string which = match_info.fetch_named("which");
llogo(this, "placeholder %s", which != null ? which : "<null>");
if(which == null) {
break;
}

switch(which) {
case "p":
result.append(pageno.to_string());
ok = true;
break;
case "%":
result.append("%");
ok = true;
break;
default:
break;
}
} while(false);

if(!ok) {
string fullmatch = match_info.fetch(0);
if(fullmatch == null) {
fullmatch = "<null>";
}
lwarningo(this, @"I don't understand the placeholder '$fullmatch'");
}

return false; // keep going
}

/**
* Render one header or footer.
*
* @param ident Which header/footer. Only used for log messages.
* @param markup The Pango markup to render
* @param widthP The width to use for the layout
* @param align The alignment to use for the layout
* @param leftI Where to render (X) with respect to the page
* @param topI Where to render (Y) with respect to the page
*/
private void render_one_hf(string ident, string markup, int widthP,
Pango.Alignment align, double leftI,
double topI)
{
if(markup == "") {
ltraceo(this, "HF %s: Skipping --- no markup", ident);
return;
}
ltraceo(this, "HF %s: Processing markup -%s-", ident, markup);

string m2; // modified markup post placeholder processing
try {
m2 = re_hf_placeholder.replace_eval(markup, -1, 0, 0,
(m, s)=>{ return replace_hf_placeholders(ident, m, s); });
} catch(RegexError e) {
lwarningo(this, "Got regex error: %s", e.message);
m2 = markup;
}

// By default, make the text smaller. The user can override this
// with an express `<span>`.
m2 = @"<span size=\"small\">$m2</span>";

ltraceo(this, "HF %s: Rendering", ident);
pageno_layout.set_width(widthP);
pageno_layout.set_alignment(align);
pageno_layout.set_markup(m2, -1);
cr.move_to(i2c(leftI), i2c(topI));
Pango.cairo_show_layout(cr, pageno_layout);
ltraceo(this, "HF %s: Done", ident);
}

///////////////////////////////////////////////////////////////////
// Generate blocks of markup from a Doc.
// The methods in this section assume cr and layout members are valid
Expand Down Expand Up @@ -560,13 +669,6 @@ namespace My {
case "":
// not a command
break;
case "header":
header_markup = blk.markup;
header_markup._strip();
linfoo(this, "Header markup set to -%s-", header_markup);
blk = null; // discard the Blk we used to collect the text
blk = new Blk(layout);
break;
default:
lwarningo(this, "Ignoring unknown command '%s'", cmd);
break;
Expand Down

0 comments on commit 5838247

Please sign in to comment.