/*
 * Copyright 2020 Michael Drake <tlsa@netsurf-browser.org>
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * NetSurf is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * NetSurf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file
 * Pave info viewer window implementation
 */

#include <stdlib.h>
#include <string.h>

#include "css/utils.h"

#include "utils/nsurl.h"
#include "utils/nscolour.h"

#include "netsurf/mouse.h"
#include "netsurf/layout.h"
#include "netsurf/keypress.h"
#include "netsurf/plotters.h"
#include "netsurf/core_window.h"
#include "netsurf/browser_window.h"

#include "desktop/knockout.h"
#include "desktop/page-info.h"
#include "desktop/gui_internal.h"
#include "desktop/system_colour.h"

/**
 * Plot style for heading font.
 */
static plot_font_style_t pi__heading[PAGE_STATE__COUNT] = {
	[PAGE_STATE_UNKNOWN] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_INTERNAL] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_LOCAL] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_INSECURE] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_SECURE_OVERRIDE] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_SECURE_ISSUES] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
	[PAGE_STATE_SECURE] = {
		.family = PLOT_FONT_FAMILY_SANS_SERIF,
		.size = 14 * PLOT_STYLE_SCALE,
		.flags = FONTF_NONE,
		.weight = 400,
	},
};

/**
 * Plot style for domain font.
 */
static plot_font_style_t pi__domain = {
	.family = PLOT_FONT_FAMILY_SANS_SERIF,
	.size = 8 * PLOT_STYLE_SCALE,
	.flags = FONTF_NONE,
	.weight = 700,
};

/**
 * Plot style for item font.
 */
static plot_font_style_t pi__item = {
	.family = PLOT_FONT_FAMILY_SANS_SERIF,
	.size = 11 * PLOT_STYLE_SCALE,
	.flags = FONTF_NONE,
	.weight = 400,
};

/**
 * Plot style for item detail font.
 */
static plot_font_style_t pi__item_detail = {
	.family = PLOT_FONT_FAMILY_SANS_SERIF,
	.size = 11 * PLOT_STYLE_SCALE,
	.flags = FONTF_NONE,
	.weight = 400,
};

/**
 * Plot style for window background.
 */
static plot_style_t pi__bg = {
	.fill_type = PLOT_OP_TYPE_SOLID,
};

/**
 * Plot style for hover background.
 */
static plot_style_t pi__hover = {
	.fill_type = PLOT_OP_TYPE_SOLID,
};

/**
 * An "text" type page info entry.
 */
struct page_info_text {
	const char *text;
	const plot_font_style_t *style;
	int width;
	int height;
	int padding_bottom;
};

/**
 * An "item" type page info entry.
 */
struct page_info_item {
	struct page_info_text item;
	struct page_info_text detail;
	const plot_style_t *hover_bg;
	int padding_bottom;
	int padding_top;
	bool hover;
};

/**
 * List of page info window entries.
 */
enum pi_entry {
	PI_ENTRY_HEADER,
	PI_ENTRY_DOMAIN,
	PI_ENTRY_CERT,
	PI_ENTRY_COOKIES,
	PI_ENTRY__COUNT,
};

/**
 * An entry on a page info window.
 */
struct page_info_entry {
	/**
	 * List of page info entry types.
	 */
	enum page_info_entry_type {
		PAGE_INFO_ENTRY_TYPE_TEXT,
		PAGE_INFO_ENTRY_TYPE_ITEM,
	} type;
	/**
	 * Type-specific page info entry data.
	 */
	union {
		struct page_info_text text;
		struct page_info_item item;
	} u;
};

/**
 * The default page info window data.
 */
struct page_info_entry pi__entries[PI_ENTRY__COUNT] = {
	[PI_ENTRY_HEADER] = {
		.type = PAGE_INFO_ENTRY_TYPE_TEXT,
	},
	[PI_ENTRY_DOMAIN] = {
		.type = PAGE_INFO_ENTRY_TYPE_TEXT,
		.u = {
			.text = {
				.style = &pi__domain,
			},
		},
	},
	[PI_ENTRY_CERT] = {
		.type = PAGE_INFO_ENTRY_TYPE_ITEM,
		.u = {
			.item = {
				.item = {
					.style = &pi__item,
				},
				.detail = {
					.style = &pi__item_detail,
				},
				.hover_bg = &pi__hover,
			},
		},
	},
	[PI_ENTRY_COOKIES] = {
		.type = PAGE_INFO_ENTRY_TYPE_ITEM,
		.u = {
			.item = {
				.item = {
					.style = &pi__item,
				},
				.detail = {
					.style = &pi__item_detail,
				},
				.hover_bg = &pi__hover,
			},
		},
	},
};

/**
 * The page info window structure.
 */
struct page_info {
	const struct core_window_callback_table *cw_t;
	struct core_window *cw_h;

	struct browser_window *bw;
	lwc_string *domain;
	enum nsurl_scheme_type scheme;

	browser_window_page_info_state state;
	unsigned cookies;

	char cookie_text[64];
	struct page_info_entry entries[PI_ENTRY__COUNT];

	int width;
	int height;

	int window_padding;
};

/* Exported interface documented in desktop/page_info.h */
nserror page_info_init(void)
{
	pi__bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__hover.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG_HOVER];

	pi__domain.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__domain.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];

	pi__item.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__item.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];

	pi__item_detail.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__item_detail.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED];

	pi__heading[PAGE_STATE_UNKNOWN].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_UNKNOWN].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD];
	pi__heading[PAGE_STATE_INTERNAL].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_INTERNAL].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];
	pi__heading[PAGE_STATE_LOCAL].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_LOCAL].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];
	pi__heading[PAGE_STATE_INSECURE].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_INSECURE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD];
	pi__heading[PAGE_STATE_SECURE_OVERRIDE].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_SECURE_OVERRIDE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD];
	pi__heading[PAGE_STATE_SECURE_ISSUES].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_SECURE_ISSUES].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_BAD];
	pi__heading[PAGE_STATE_SECURE].background = nscolours[NSCOLOUR_WIN_EVEN_BG];
	pi__heading[PAGE_STATE_SECURE].foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_GOOD];

	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_fini(void)
{
	return NSERROR_OK;
}

/**
 * Measure the text in the page_info window.
 *
 * \param[in] pi  The page info window handle.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__measure_text_entry(
		struct page_info_text *pit)
{
	nserror err;
	int height_px;

	err = guit->layout->width(pit->style,
			pit->text, strlen(pit->text),
			&pit->width);
	if (err != NSERROR_OK) {
		return err;
	}

	/* \todo: This needs to be a helper in plot style or in nscss. */
	height_px = ((pit->style->size / PLOT_STYLE_SCALE) *
			FIXTOINT(nscss_screen_dpi) + 36) / 72;

	pit->height = (height_px * 8 + 3) / 6;

	return NSERROR_OK;
}

/**
 * Measure the text in the page_info window.
 *
 * \param[in] pi  The page info window handle.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__measure_text(
		struct page_info *pi)
{
	nserror err;

	for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) {
		struct page_info_entry *entry = pi->entries + i;
		int padding;

		switch (entry->type) {
		case PAGE_INFO_ENTRY_TYPE_TEXT:
			err = page_info__measure_text_entry(
					&entry->u.text);
			if (err != NSERROR_OK) {
				return err;
			}
			if (i == PI_ENTRY_DOMAIN) {
				entry->u.text.padding_bottom =
						entry->u.text.height * 3 / 2;
			}
			break;

		case PAGE_INFO_ENTRY_TYPE_ITEM:
			err = page_info__measure_text_entry(
					&entry->u.item.item);
			if (err != NSERROR_OK) {
				return err;
			}
			err = page_info__measure_text_entry(
					&entry->u.item.detail);
			if (err != NSERROR_OK) {
				return err;
			}
			padding = entry->u.item.item.height / 4;
			entry->u.item.padding_top = padding;
			entry->u.item.padding_bottom = padding;

			break;
		}
	}

	pi->window_padding = pi->entries[PI_ENTRY_DOMAIN]
			.u.item.item.height / 2;

	return NSERROR_OK;
}

/**
 * Set the text for the page_info window.
 *
 * \todo Use messages for internationalisation.
 *
 * \param[in] pi  The page info window handle.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__set_text(
		struct page_info *pi)
{
	int printed;
	static const char *header[PAGE_STATE__COUNT] = {
		[PAGE_STATE_UNKNOWN]         = "Provenance unknown",
		[PAGE_STATE_INTERNAL]        = "NetSurf data",
		[PAGE_STATE_LOCAL]           = "Local data",
		[PAGE_STATE_INSECURE]        = "Connection not secure",
		[PAGE_STATE_SECURE_OVERRIDE] = "Connection not secure",
		[PAGE_STATE_SECURE_ISSUES]   = "Connection not secure",
		[PAGE_STATE_SECURE]          = "Connection is secure",
	};
	static const char *certificate[PAGE_STATE__COUNT] = {
		[PAGE_STATE_UNKNOWN]         = "Missing",
		[PAGE_STATE_INTERNAL]        = "None",
		[PAGE_STATE_LOCAL]           = "None",
		[PAGE_STATE_INSECURE]        = "Not valid",
		[PAGE_STATE_SECURE_OVERRIDE] = "Not valid",
		[PAGE_STATE_SECURE_ISSUES]   = "Not valid",
		[PAGE_STATE_SECURE]          = "Valid",
	};

	assert(pi != NULL);
	assert(pi->state < PAGE_STATE__COUNT);

	pi->entries[PI_ENTRY_HEADER].u.text.style = &pi__heading[pi->state];
	pi->entries[PI_ENTRY_HEADER].u.text.text = header[pi->state];
	pi->entries[PI_ENTRY_DOMAIN].u.text.text = (pi->domain) ?
			lwc_string_data(pi->domain) : "<No domain>";

	pi->entries[PI_ENTRY_CERT].u.item.item.text = "Certificate: ";
	pi->entries[PI_ENTRY_CERT].u.item.detail.text = certificate[pi->state];

	printed = snprintf(pi->cookie_text, sizeof(pi->cookie_text),
			"(%u in use)", pi->cookies);
	if (printed < 0) {
		return NSERROR_UNKNOWN;

	} else if ((unsigned) printed >= sizeof(pi->cookie_text)) {
		return NSERROR_NOSPACE;
	}
	pi->entries[PI_ENTRY_COOKIES].u.item.item.text = "Cookies: ";
	pi->entries[PI_ENTRY_COOKIES].u.item.detail.text = pi->cookie_text;

	return page_info__measure_text(pi);
}

/**
 * Create page info from a browser window.
 *
 * \param[in] pi  The page info window handle.
 * \param[in] bw  Browser window to show page info for.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__create_from_bw(
		struct page_info *pi,
		struct browser_window *bw)
{
	nsurl *url = browser_window_access_url(bw);

	pi->bw = bw;
	pi->state = browser_window_get_page_info_state(bw);
	pi->cookies = browser_window_get_cookie_count(bw);
	pi->domain = nsurl_get_component(url, NSURL_HOST);
	pi->scheme = nsurl_get_scheme_type(url);

	return page_info__set_text(pi);
}

/**
 * Check whether an entry is relevant.
 *
 * \param[in] entry   The page info entry to consider.
 * \param[in] scheme  URL scheme that the page info is for.
 * \return true if the entry should be hidden, otherwise false.
 */
static inline bool page_info__hide_entry(
		enum pi_entry entry,
		enum nsurl_scheme_type scheme)
{
	switch (entry) {
	case PI_ENTRY_CERT:
		if (scheme != NSURL_SCHEME_HTTPS) {
			return true;
		}
		break;
	case PI_ENTRY_COOKIES:
		if (scheme != NSURL_SCHEME_HTTP &&
		    scheme != NSURL_SCHEME_HTTPS) {
			return true;
		}
		break;
	default:
		break;
	}

	return false;
}

/**
 * Lay out the page info window.
 *
 * \param[in] pi  The page info window handle.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__layout(
		struct page_info *pi)
{
	int cur_y = 0;
	int max_x = 0;

	cur_y += pi->window_padding;
	for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) {
		struct page_info_entry *entry = pi->entries + i;

		if (page_info__hide_entry(i, pi->scheme)) {
			continue;
		}

		switch (entry->type) {
		case PAGE_INFO_ENTRY_TYPE_TEXT:
			cur_y += entry->u.text.height;
			if (max_x < entry->u.text.width) {
				max_x = entry->u.text.width;
			}
			cur_y += entry->u.text.padding_bottom;
			break;

		case PAGE_INFO_ENTRY_TYPE_ITEM:
		{
			int full_width = entry->u.item.item.width +
					entry->u.item.detail.width;
			cur_y += entry->u.item.padding_top;
			cur_y += entry->u.item.item.height;
			if (max_x < full_width) {
				max_x = full_width;
			}
			cur_y += entry->u.item.padding_bottom;
		}
			break;
		}
	}
	cur_y += pi->window_padding;
	max_x += pi->window_padding * 2;

	pi->width = max_x;
	pi->height = cur_y;
	return pi->cw_t->update_size(pi->cw_h, max_x, cur_y);
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_create(
		const struct core_window_callback_table *cw_t,
		struct core_window *cw_h,
		struct browser_window *bw,
		struct page_info **pi_out)
{
	struct page_info *pi;
	nserror err;

	pi = calloc(1, sizeof(*pi));
	if (pi == NULL) {
		return NSERROR_NOMEM;
	}

	pi->cw_t = cw_t;
	pi->cw_h = cw_h;

	memcpy(pi->entries, pi__entries, sizeof(pi__entries));

	err = page_info__create_from_bw(pi, bw);
	if (err != NSERROR_OK) {
		page_info_destroy(pi);
		return err;
	}

	err = page_info__layout(pi);
	if (err != NSERROR_OK) {
		page_info_destroy(pi);
		return err;
	}

	*pi_out = pi;
	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_destroy(struct page_info *pi)
{
	if (pi->domain != NULL) {
		lwc_string_unref(pi->domain);
	}
	free(pi);
	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_set(struct page_info *pgi, struct browser_window *bw)
{
	nserror res;

	if (pgi->domain != NULL) {
		lwc_string_unref(pgi->domain);
	}

	res = page_info__create_from_bw(pgi, bw);
	if (res == NSERROR_OK) {
		res = page_info__layout(pgi);
	}

	return res;
}

/**
 * Render a text entry.
 *
 * \param[in] pit  The page info window handle.
 * \param[in] x    X-coordinate to plot at.
 * \param[in] y    Y-coordinate to plot at.
 * \param[in] ctx  Current redraw context.
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__redraw_text_entry(
		const struct page_info_text *pit,
		int x,
		int y,
		const struct redraw_context *ctx)
{
	int baseline = (pit->height * 3 + 2) / 4;

	ctx->plot->text(ctx, pit->style, x, y + baseline,
			pit->text, strlen(pit->text));

	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_redraw(
		const struct page_info *pi,
		int x,
		int y,
		const struct rect *clip,
		const struct redraw_context *ctx)
{
	struct redraw_context new_ctx = *ctx;
	struct rect r = {
		.x0 = clip->x0 + x,
		.y0 = clip->y0 + y,
		.x1 = clip->x1 + x,
		.y1 = clip->y1 + y,
	};
	int cur_y = y;
	nserror err;

	/* Start knockout rendering if it's available for this plotter. */
	if (ctx->plot->option_knockout) {
		bool res = knockout_plot_start(ctx, &new_ctx);
		if (res == false) {
			return NSERROR_UNKNOWN;
		}
	}

	/* Set up clip rectangle and draw background. */
	new_ctx.plot->clip(&new_ctx, &r);
	new_ctx.plot->rectangle(&new_ctx, &pi__bg, &r);

	cur_y += pi->window_padding;
	for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) {
		const struct page_info_entry *entry = pi->entries + i;
		int cur_x = x + pi->window_padding;

		if (page_info__hide_entry(i, pi->scheme)) {
			continue;
		}

		switch (entry->type) {
		case PAGE_INFO_ENTRY_TYPE_TEXT:
			err = page_info__redraw_text_entry(
					&entry->u.text,
					cur_x, cur_y,
					&new_ctx);
			if (err != NSERROR_OK) {
				goto cleanup;
			}
			cur_y += entry->u.text.height;
			cur_y += entry->u.text.padding_bottom;
			break;

		case PAGE_INFO_ENTRY_TYPE_ITEM:
			if (entry->u.item.hover) {
				r.y0 = cur_y;
				r.y1 = cur_y + entry->u.item.padding_top +
						entry->u.item.item.height +
						entry->u.item.padding_bottom;
				new_ctx.plot->rectangle(&new_ctx,
						&pi__hover, &r);
			}
			cur_y += entry->u.item.padding_top;
			err = page_info__redraw_text_entry(
					&entry->u.item.item,
					cur_x, cur_y,
					&new_ctx);
			if (err != NSERROR_OK) {
				goto cleanup;
			}
			cur_x += entry->u.item.item.width;
			err = page_info__redraw_text_entry(
					&entry->u.item.detail,
					cur_x, cur_y,
					&new_ctx);
			if (err != NSERROR_OK) {
				goto cleanup;
			}
			cur_y += entry->u.item.item.height;
			cur_y += entry->u.item.padding_bottom;
			break;
		}
	}

cleanup:
	/* Rendering complete */
	if (ctx->plot->option_knockout) {
		bool res = knockout_plot_end(ctx);
		if (res == false) {
			return NSERROR_UNKNOWN;
		}
	}

	return NSERROR_OK;
}

/**
 * Handle any clicks on an item.
 *
 * \param[in] pi       The page info window handle.
 * \param[in] mouse    The current mouse state.
 * \param[in] clicked  The page info window entry to consider clicks on.
 * \param[out] did_something Set to true if this click did something
 * \return NSERROR_OK on success, appropriate error code otherwise.
 */
static nserror page_info__handle_item_click(
		struct page_info *pi,
		enum browser_mouse_state mouse,
		enum pi_entry clicked,
		bool *did_something)
{
	nserror err;

	if (!(mouse & BROWSER_MOUSE_CLICK_1)) {
		return NSERROR_OK;
	}

	switch (clicked) {
	case PI_ENTRY_CERT:
		err = browser_window_show_certificates(pi->bw);
		*did_something = true;
		break;
	case PI_ENTRY_COOKIES:
		err = browser_window_show_cookies(pi->bw);
		*did_something = true;
		break;
	default:
		err = NSERROR_OK;
		break;
	}

	return err;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_mouse_action(
		struct page_info *pi,
		enum browser_mouse_state mouse,
		int x,
		int y,
		bool *did_something)
{
	int cur_y = 0;
	nserror err;

	cur_y += pi->window_padding;
	for (unsigned i = 0; i < PI_ENTRY__COUNT; i++) {
		struct page_info_entry *entry = pi->entries + i;
		bool hovering = false;
		int height;

		if (page_info__hide_entry(i, pi->scheme)) {
			continue;
		}

		switch (entry->type) {
		case PAGE_INFO_ENTRY_TYPE_TEXT:
			cur_y += entry->u.text.height;
			cur_y += entry->u.text.padding_bottom;
			break;

		case PAGE_INFO_ENTRY_TYPE_ITEM:
			height = entry->u.item.padding_top +
			         entry->u.item.item.height +
			         entry->u.item.padding_bottom;

			if (y >= cur_y && y < cur_y + height) {
				hovering = true;
				err = page_info__handle_item_click(
						pi, mouse, i, did_something);
				if (err != NSERROR_OK) {
					return err;
				}
			}
			if (entry->u.item.hover != hovering) {
				int w, h;
				struct rect r = {
					.x0 = 0,
					.y0 = cur_y,
					.y1 = cur_y + height,
				};
				pi->cw_t->get_window_dimensions(
						pi->cw_h, &w, &h);
				r.x1 = (pi->width > w) ? pi->width : w;

				pi->cw_t->invalidate(pi->cw_h, &r);
			}
			entry->u.item.hover = hovering;
			cur_y += height;
			break;
		}
	}

	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
bool page_info_keypress(
		struct page_info *pi,
		int32_t key)
{
	return NSERROR_OK;
}

/* Exported interface documented in desktop/page_info.h */
nserror page_info_get_size(
		struct page_info *pi,
		int *width,
		int *height)
{
	*width = pi->width;
	*height = pi->height;

	return NSERROR_OK;
}
