# Pretty-print Meal-Master recipe to .RTF format
# Usage: awk -f mmrtf.awk <recipe.mmf >recipe.rtf
# Only works with single-column Meal-Master format
#
# RTF hints:
#
# *
https://metacpan.org/dist/RTF-Writer/view/lib/RTF/Cookbook.pod
# *
https://en.wikipedia.org/wiki/Rich_Text_Format
#
# RTF uses the ANSI character set aka Windows-1252.
# To convert a recipe from UTF-8 on DOS or Linux:
#
# DOS:
# C:\> copy recipe.mmf recipe.bak
# C:\> utf8tocp.exe 1252 recipe.mmf
#
# Linux:
# $ cp recipe.mmf recipe.bak
# $ iconv -f UTF-8 -t WINDOWS-1252 <recipe.bak >recipe.mmf
function amount_format(amt) {
retval = trim(amt)
if (retval ~ /^[0-9]+$/) {
retval = retval " "
}
return retval
}
function array_size(a) {
retval = 0
for (i in a) {
retval++
}
return retval
}
function heading_fontsize(size) {
retval = fontsize + (7 - size)
return retval
}
function inches(value) {
# 1440 twips per inch
retval = value * 1440
return retval
}
function ingredient_parse(line) {
new_amount = substr(line, 1, 7)
new_unit = substr(line, 9, 2)
new_ingredient = substr(line, 12)
if (new_amount == " " && new_unit == " " && match(new_ingredient, /^[ \t]*- */)) {
ingredient = ingredient " " substr(new_ingredient, RLENGTH+1)
is_continuation = 1
} else {
amount = new_amount
unit = new_unit
ingredient = new_ingredient
is_continuation = 0
}
return
}
function rtf() {
fontsize = 24
heading_bottom = inches(1/4)
par_bottom = inches(1/8)
printf "{\\rtf1\\ansi\\deff0\\deflang1033\\windowctrl\n"
printf "{\\*\\comment generator: mmrtf.awk}\n"
printf "{\\fonttbl\n"
printf "{\\f0 \\froman Times New Roman;}\n"
printf "{\\f1 \\fmodern Line Printer;}\n"
printf "{\\f2 \\froman Symbol;}\n"
printf "{\\f3 \\fswiss Ariel;}\n"
printf "}\n"
heading_top = 0
rtf_heading(2, title)
heading_top = inches(1/4)
table_gap = 0
table_left = 0
cell1 = inches(1.6)
cell2 = inches(7)
# beginning of table
printf "{\\pard\n"
printf "\\trowd\\trgaph%d\\trleft%d\\cellx%d\\cellx%d\n",
table_gap, table_left, cell1, cell2
if (length(categories) > 0) {
printf "\\pard\\intbl{\\b{Categories:}}\\cell\n"
printf "\\pard\\intbl{%s}\\cell\n", rtf_encode(categories)
printf "\\row\n"
}
if (length(yield) > 0) {
printf "\\pard\\intbl{\\b{Yield:}}\\cell\n"
printf "\\pard\\intbl{%s}\\cell\n", rtf_encode(yield)
printf "\\row\n"
}
# end of table
printf "}\n"
if (array_size(ingredients) > 0) {
rtf_heading(3, "Ingredients")
# beginning of table
cell1 = inches(1.25)
cell2 = inches(2.5)
cell3 = inches(7)
printf "{\\pard\n"
printf "\\trowd\\trgaph%d\\trleft%d\\cellx%d\\cellx%d\\cellx%d\n",
table_gap, table_left, cell1, cell2, cell3
printf "\\pard\\intbl{\\b{Amt}}\\cell\n"
printf "\\pard\\intbl{\\b{Unit}}\\cell\n"
printf "\\pard\\intbl{\\b{Ingredient}}\\cell\n"
printf "\\row\n"
for (gsection in gsections) {
printf "\\trowd\\trgaph%d\\trleft%d\n", table_gap, table_left
printf "\\clmgf\\cellx%d\n", cell1
printf "\\clmrg\\cellx%d\n", cell2
printf "\\clmrg\\cellx%d\n", cell3
printf "\\pard\\intbl{\\i{%s}}\\cell\n", rtf_encode(gsection)
printf "\\pard\\intbl{}\\cell\n"
printf "\\pard\\intbl{}\\cell\n"
printf "\\row\n"
ingredient_count = gsections[gsection]
for (i = 1; i <= ingredient_count; i++) {
amount = amounts[gsection, i]
unit = units[gsection, i]
ingredient = ingredients[gsection, i]
printf "\\trowd\\trgaph%d\\trleft%d\\cellx%d\\cellx%d\\cellx%d\n",
table_gap, table_left, cell1, cell2, cell3
printf "\\pard\\intbl{%7s}\\cell\n",
rtf_encode(amount_format(amount))
printf "\\pard\\intbl{%s}\\cell\n", unit_name(unit)
printf "\\pard\\intbl{%s}\\cell\n", rtf_encode(ingredient)
printf "\\row\n"
}
}
# end of table
printf "}\n"
}
if (array_size(instructions) > 0) {
rtf_heading(3, "Instructions")
list_open = 0
par_open = 0
for (tsection in tsections) {
rtf_close()
if (length(tsection) > 0) {
rtf_heading(4, tsection)
}
instruction_count = tsections[tsection]
for (i = 1; i <= instruction_count; i++) {
line = instructions[tsection,i]
if (line ~ /^[ \t]*$/) {
if (list_open || par_open) {
rtf_close()
}
} else if (match(line, /^[ \t]*\* /)) {
if (par_open == 1) {
rtf_close()
}
if (list_open == 0) {
list_open = 1
printf "{\n"
}
text = substr(line, RLENGTH+1)
printf "{\\pard\\sa%d\\bullet %s\\par}\n",
par_bottom, rtf_encode(text)
} else {
if (list_open) {
rtf_close()
}
if (par_open == 0) {
printf "{\\pard\\sa%d\n", par_bottom
par_open = 1
}
printf "{%s} \n", rtf_encode(trim(line))
}
}
}
rtf_close()
}
printf "\n}\n"
return
}
function rtf_close() {
if (list_open) {
printf "}\n"
list_open = 0
} else if (par_open) {
printf "\\par}\n"
par_open = 0
}
return
}
function rtf_encode(str) {
gsub(/\\/, "\\\\", str)
gsub(/{/, "\\{", str)
gsub(/}/, "\\}", str)
return str
}
# rtf_heading(2, txt) is like HTML: printf "<h2>%s</h2>", txt
function rtf_heading(lvl, str) {
printf "{\\pard\\sa%d\\sb%d\\fs%d\\b{%s}\\par}\n",
heading_bottom,
heading_top,
heading_fontsize(lvl),
rtf_encode(str)
return
}
function trim(str) {
retval = str
gsub(/^[ \t]+/, "", retval)
gsub(/[ \t]+$/, "", retval)
return retval
}
function unit_name(unit) {
if (unit in names) {
retval = names[unit]
} else {
retval = unit
}
return retval
}
function unit_names_init() {
names["x "] = "per serving"
names["sm"] = "small"
names["md"] = "medium"
names["lg"] = "large"
names["cn"] = "can"
names["pk"] = "package"
names["pn"] = "pinch"
names["dr"] = "drop"
names["ds"] = "dash"
names["ct"] = "carton"
names["bn"] = "bunch"
names["sl"] = "slice"
names["ea"] = "each"
names["t "] = "teaspoon"
names["ts"] = "teaspoon"
names["T "] = "tablespoon"
names["tb"] = "tablespoon"
names["fl"] = "fluid ounce"
names["c "] = "cup"
names["pt"] = "pint"
names["qt"] = "quart"
names["ga"] = "gallon"
names["oz"] = "ounce"
names["lb"] = "pound"
names["ml"] = "milliliter"
names["cb"] = "cubic cm"
names["cl"] = "centiliter"
names["dl"] = "deciliter"
names["l "] = "liter"
names["mg"] = "milligram"
names["cg"] = "centigram"
names["dg"] = "decigram"
names["g "] = "gram"
names["kg"] = "kilogram"
return
}
BEGIN {
after_ingredients = 0
at_end = 0
in_gsection = 0
in_heading = 0
in_ingredients = 0
in_instructions = 0
in_list = 0
unit_names_init()
}
{
gsub(/\r/, "")
if (NR == 1) {
if (/^(MMMMM|-----)----- Recipe via Meal-Master/) {
in_heading = 1
} else {
print "Error: Not in Meal-Master format\n"
exit 0
}
} else if (NR == 2) {
# ignore second line
} else if (in_heading) {
if (match($0, /^[ \t]+Title: /)) {
title = substr($0, RLENGTH+1)
} else if (match($0, /^[ \t]+Categories: /)) {
categories = substr($0, RLENGTH+1)
} else if (match($0, /^[ \t]+Yield: /)) {
yield = substr($0, RLENGTH+1)
} else if (/^[ \t]*$/) {
in_heading = 0
in_ingredients = 1
}
} else if (in_ingredients) {
if (match($0, /^(MMMMM|-----)-+/)) {
in_ingredients = 0
in_gsection = 1
gsection = substr($0, RLENGTH+1)
gsub(/-+$/, "", gsection)
} else if (/^[ \t]*$/) {
in_ingredients = 0
after_ingredients = 1
} else {
gsection = ""
i = gsections[gsection]
ingredient_parse($0)
if (is_continuation == 0) {
i++
gsections[gsection] = i
}
amounts[gsection,i] = amount
units[gsection,i] = unit
ingredients[gsection,i] = ingredient
}
} else if (after_ingredients) {
if (match($0, /^(MMMMM|-----)-+/)) {
after_ingredients = 0
in_gsection = 1
gsection = substr($0, RLENGTH+1)
gsub(/-+$/, "", gsection)
} else {
after_ingredients = 0
in_instructions = 1
}
} else if (in_gsection) {
if (match($0, /^(MMMMM|-----)-+/)) {
in_gsection = 1
gsection = substr($0, RLENGTH+1)
gsub(/-+$/, "", gsection)
} else if (/^[ \t]*$/) {
in_gsection = 0
after_gsection = 1
} else {
i = gsections[gsection]
ingredient_parse($0)
if (is_continuation == 0) {
i++
gsections[gsection] = i
}
amounts[gsection,i] = amount
units[gsection,i] = unit
ingredients[gsection,i] = ingredient
}
} else if (after_gsection) {
if (match($0, /^(MMMMM|-----)-+/)) {
after_gsection = 0
in_gsection = 1
gsection = substr($0, RLENGTH+1)
gsub(/-+$/, "", gsection)
} else {
after_gsection = 0
in_instructions = 1
}
}
if (in_instructions) {
if (match($0, /^(MMMMM|-----)-+/)) {
tsection = substr($0, RLENGTH+1)
gsub(/-+$/, "", tsection)
} else if (/^(MMMMM|-----)$/) {
in_instructions = 0
at_end = 1
} else if (/^[ \t]*\* /) {
in_instructions = 0
in_list = 1
} else {
tsections[tsection]++
i = tsections[tsection]
instructions[tsection,i] = $0
}
}
if (in_list) {
if (/^[ \t]*$/) {
in_list = 0
in_instructions = 1
tsections[tsection]++
i = tsections[tsection]
instructions[tsection,i] = $0
} else if (/^[ \t]*\* /) {
tsections[tsection]++
i = tsections[tsection]
instructions[tsection,i] = $0
} else {
i = tsections[tsection]
line = trim($0)
instructions[tsection,i] = instructions[tsection,i] " " line
}
}
}
END {
rtf()
}