/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * load_ppt.c - 
 * Copyright (C) 2003, Christopher James Lahey
 *
 * Authors:
 *   Christopher James Lahey <clahey@ximian.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU Library General Public
 * License as published by the Free Software Foundation.
 *
 * This file 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this file; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 **/

#include <config.h>
#include "load-ppt.h"
#include "ppt-types.h"
#include "god-drawing-ms-client-handler-ppt.h"
#include <goffice/utils/go-units.h>
#include "ppt-parsing-helper.h"

#include <gsf/gsf-input-stdio.h>
#include <gsf/gsf-input-memory.h>
#include <gsf/gsf-utils.h>
#include <gsf/gsf-infile.h>
#include <gsf/gsf-infile-msole.h>
#include <gsf/gsf-msole-utils.h>
#include <goffice/ms-compat/god-drawing-ms.h>
#include <goffice/ms-compat/go-ms-parser.h>

#include <string.h>

#define ERROR(conditional, message) if (!(conditional)) { g_warning ((message)); return; }

static const GOMSParserRecordType types[] =
{
	/*	{	Unknown,			"Unknown",			FALSE,	FALSE,	NULL,	-1,	-1,	-1	}, */
	{	Document,			"Document",			TRUE,	FALSE,	-1,	-1	},
	{	DocumentAtom,			"DocumentAtom",			FALSE,	TRUE,	-1,	-1	},
	{	EndDocument,			"EndDocument",			FALSE,	FALSE,	-1,	-1	},
	{	Slide,				"Slide",			TRUE,	FALSE,	-1,	-1	},
	{	SlideAtom,			"SlideAtom",			FALSE,	FALSE,	-1,	-1	},
	{	Notes,				"Notes",			TRUE,	FALSE,	-1,	-1	},
	{	NotesAtom,			"NotesAtom",			FALSE,	FALSE,	-1,	-1	},
	{	Environment,			"Environment",			TRUE,	FALSE,	-1,	-1	},
	{	SlidePersistAtom,		"SlidePersistAtom",		FALSE,	TRUE,	20,	20	},
	{	SSlideLayoutAtom,		"SSlideLayoutAtom",		FALSE,	FALSE,	-1,	-1	},
	{	MainMaster,			"MainMaster",			TRUE,	FALSE,	-1,	-1	},
	{	SSSlideInfoAtom,		"SSSlideInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	SlideViewInfo,			"SlideViewInfo",		TRUE,	FALSE,	-1,	-1	},
	{	GuideAtom,			"GuideAtom",			FALSE,	FALSE,	-1,	-1	},
	{	ViewInfo,			"ViewInfo",			TRUE,	FALSE,	-1,	-1	},
	{	ViewInfoAtom,			"ViewInfoAtom",			FALSE,	FALSE,	-1,	-1	},
	{	SlideViewInfoAtom,		"SlideViewInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	VBAInfo,			"VBAInfo",			TRUE,	FALSE,	-1,	-1	},
	{	VBAInfoAtom,			"VBAInfoAtom",			FALSE,	FALSE,	-1,	-1	},
	{	SSDocInfoAtom,			"SSDocInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	Summary,			"Summary",			TRUE,	FALSE,	-1,	-1	},
	{	DocRoutingSlip,			"DocRoutingSlip",		FALSE,	FALSE,	-1,	-1	},
	{	OutlineViewInfo,		"OutlineViewInfo",		TRUE,	FALSE,	-1,	-1	},
	{	SorterViewInfo,			"SorterViewInfo",		TRUE,	FALSE,	-1,	-1	},
	{	ExObjList,			"ExObjList",			TRUE,	FALSE,	-1,	-1	},
	{	ExObjListAtom,			"ExObjListAtom",		FALSE,	FALSE,	-1,	-1	},
	{	PPDrawingGroup,			"PPDrawingGroup",		FALSE,	FALSE,	-1,	-1	}, // Escher
	{	PPDrawing,			"PPDrawing",			FALSE,	FALSE,	-1,	-1	}, // Escher
	{	NamedShows,			"NamedShows",			FALSE,	FALSE,	-1,	-1	}, // don't know if container
	{	NamedShow,			"NamedShow",			TRUE,	FALSE,	-1,	-1	},
	{	NamedShowSlides,		"NamedShowSlides",		FALSE,	FALSE,	-1,	-1	}, // don't know if container
	{	List,				"List",				TRUE,	FALSE,	-1,	-1	},
	{	FontCollection,			"FontCollection",		TRUE,	FALSE,	-1,	-1	},
	{	BookmarkCollection,		"BookmarkCollection",		TRUE,	FALSE,	-1,	-1	},
	{	SoundCollAtom,			"SoundCollAtom",		FALSE,	FALSE,	-1,	-1	},
	{	Sound,				"Sound",			TRUE,	FALSE,	-1,	-1	},
	{	SoundData,			"SoundData",			FALSE,	FALSE,	-1,	-1	},
	{	BookmarkSeedAtom,		"BookmarkSeedAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ColorSchemeAtom,		"ColorSchemeAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExObjRefAtom,			"ExObjRefAtom",			FALSE,	FALSE,	-1,	-1	},
	{	OEShapeAtom,			"OEShapeAtom",			FALSE,	FALSE,	-1,	-1	},
	{	OEPlaceholderAtom,		"OEPlaceholderAtom",		FALSE,	FALSE,	-1,	-1	},
	{	GPointAtom,			"GPointAtom",			FALSE,	FALSE,	-1,	-1	},
	{	GRatioAtom,			"GRatioAtom",			FALSE,	FALSE,	-1,	-1	},
	{	OutlineTextRefAtom,		"OutlineTextRefAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TextHeaderAtom,			"TextHeaderAtom",		FALSE,	TRUE,	4,	4	},
	{	TextCharsAtom,			"TextCharsAtom",		FALSE,	TRUE,	-1,	-1	},
	{	StyleTextPropAtom,		"StyleTextPropAtom",		FALSE,	TRUE,	-1,	-1	},
	{	BaseTextPropAtom,		"BaseTextPropAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TxMasterStyleAtom,		"TxMasterStyleAtom",		FALSE,	TRUE,	-1,	-1	},
	{	TxCFStyleAtom,			"TxCFStyleAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TxPFStyleAtom,			"TxPFStyleAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TextRulerAtom,			"TextRulerAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TextBookmarkAtom,		"TextBookmarkAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TextBytesAtom,			"TextBytesAtom",		FALSE,	TRUE,	-1,	-1	},
	{	TxSIStyleAtom,			"TxSIStyleAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TextSpecInfoAtom,		"TextSpecInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	DefaultRulerAtom,		"DefaultRulerAtom",		FALSE,	FALSE,	-1,	-1	},
	{	FontEntityAtom,			"FontEntityAtom",		FALSE,	TRUE,	-1,	-1	},
	{	FontEmbeddedData,		"FontEmbeddedData",		FALSE,	FALSE,	-1,	-1	},
	{	CString,			"CString",			FALSE,	FALSE,	-1,	-1	},
	{	MetaFile,			"MetaFile",			FALSE,	FALSE,	-1,	-1	},
	{	ExOleObjAtom,			"ExOleObjAtom",			FALSE,	FALSE,	-1,	-1	},
	{	SrKinsoku,			"SrKinsoku",			TRUE,	FALSE,	-1,	-1	},
	{	HandOut,			"HandOut",			TRUE,	FALSE,	-1,	-1	},
	{	ExEmbed,			"ExEmbed",			TRUE,	FALSE,	-1,	-1	},
	{	ExEmbedAtom,			"ExEmbedAtom",			FALSE,	FALSE,	-1,	-1	},
	{	ExLink,				"ExLink",			TRUE,	FALSE,	-1,	-1	},
	{	BookmarkEntityAtom,		"BookmarkEntityAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExLinkAtom,			"ExLinkAtom",			FALSE,	FALSE,	-1,	-1	},
	{	SrKinsokuAtom,			"SrKinsokuAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExHyperlinkAtom,		"ExHyperlinkAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExHyperlink,			"ExHyperlink",			TRUE,	FALSE,	-1,	-1	},
	{	SlideNumberMCAtom,		"SlideNumberMCAtom",		FALSE,	FALSE,	-1,	-1	},
	{	HeadersFooters,			"HeadersFooters",		TRUE,	FALSE,	-1,	-1	},
	{	HeadersFootersAtom,		"HeadersFootersAtom",		FALSE,	FALSE,	-1,	-1	},
	{	TxInteractiveInfoAtom,		"TxInteractiveInfoAtom",	FALSE,	FALSE,	-1,	-1	},
	{	CharFormatAtom,			"CharFormatAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ParaFormatAtom,			"ParaFormatAtom",		FALSE,	FALSE,	-1,	-1	},
	{	RecolorInfoAtom,		"RecolorInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExQuickTimeMovie,		"ExQuickTimeMovie",		TRUE,	FALSE,	-1,	-1	},
	{	ExQuickTimeMovieData,		"ExQuickTimeMovieData",		FALSE,	FALSE,	-1,	-1	},
	{	ExControl,			"ExControl",			TRUE,	FALSE,	-1,	-1	},
	{	SlideListWithText,		"SlideListWithText",		TRUE,	FALSE,	-1,	-1	},
	{	InteractiveInfo,		"InteractiveInfo",		TRUE,	FALSE,	-1,	-1	},
	{	InteractiveInfoAtom,		"InteractiveInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	UserEditAtom,			"UserEditAtom",			FALSE,	FALSE,	-1,	-1	},
	{	CurrentUserAtom,		"CurrentUserAtom",		FALSE,	FALSE,	-1,	-1	},
	{	DateTimeMCAtom,			"DateTimeMCAtom",		FALSE,	FALSE,	-1,	-1	},
	{	GenericDateMCAtom,		"GenericDateMCAtom",		FALSE,	FALSE,	-1,	-1	},
	{	FooterMCAtom,			"FooterMCAtom",			FALSE,	FALSE,	-1,	-1	},
	{	ExControlAtom,			"ExControlAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExMediaAtom,			"ExMediaAtom",			FALSE,	FALSE,	-1,	-1	},
	{	ExVideo,			"ExVideo",			TRUE,	FALSE,	-1,	-1	},
	{	ExAviMovie,			"ExAviMovie",			TRUE,	FALSE,	-1,	-1	},
	{	ExMCIMovie,			"ExMCIMovie",			TRUE,	FALSE,	-1,	-1	},
	{	ExMIDIAudio,			"ExMIDIAudio",			TRUE,	FALSE,	-1,	-1	},
	{	ExCDAudio,			"ExCDAudio",			TRUE,	FALSE,	-1,	-1	},
	{	ExWAVAudioEmbedded,		"ExWAVAudioEmbedded",		TRUE,	FALSE,	-1,	-1	},
	{	ExWAVAudioLink,			"ExWAVAudioLink",		TRUE,	FALSE,	-1,	-1	},
	{	ExOleObjStg,			"ExOleObjStg",			FALSE,	FALSE,	-1,	-1	},
	{	ExCDAudioAtom,			"ExCDAudioAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ExWAVAudioEmbeddedAtom,		"ExWAVAudioEmbeddedAtom",	FALSE,	FALSE,	-1,	-1	},
	{	AnimationInfoAtom,		"AnimationInfoAtom",		FALSE,	FALSE,	-1,	-1	},
	{	RTFDateTimeMCAtom,		"RTFDateTimeMCAtom",		FALSE,	FALSE,	-1,	-1	},
	{	ProgTags,			"ProgTags",			FALSE,	FALSE,	-1,	-1	}, // don't know if container
	{	ProgStringTag,			"ProgStringTag",		TRUE,	FALSE,	-1,	-1	},
	{	ProgBinaryTag,			"ProgBinaryTag",		TRUE,	FALSE,	-1,	-1	},
	{	BinaryTagData,			"BinaryTagData",		FALSE,	FALSE,	-1,	-1	},
	{	PrintOptions,			"PrintOptions",			FALSE,	FALSE,	-1,	-1	},
	{	PersistPtrFullBlock,		"PersistPtrFullBlock",		FALSE,	FALSE,	-1,	-1	}, // don't know if container
	{	PersistPtrIncrementalBlock,	"PersistPtrIncrementalBlock",	FALSE,	FALSE,	-1,	-1	},
	{	GScalingAtom,			"GScalingAtom",			FALSE,	FALSE,	-1,	-1	},
	{	GRColorAtom,			"GRColorAtom",			FALSE,	FALSE,	-1,	-1	},
};

typedef struct {
	PresentSlide *current_slide;
	int current_slide_text_count;
	PresentText  *current_text;
} SlideListWithTextParseState;

typedef struct {
	PresentSlide *slide;
} SlideParseState;

typedef struct {
	PresentPresentation *presentation;
	int slides_read;
	GPtrArray *fonts;
} ParseUserData;

static void
slide_list_with_text_parse_state_finish_text (PresentPresentation *presentation, SlideListWithTextParseState *parse_state)
{
	if (parse_state->current_text) {
		ERROR (parse_state->current_slide, "Parse Error 1");
		present_slide_append_text (parse_state->current_slide, parse_state->current_text);
		g_object_unref (parse_state->current_text);
		parse_state->current_text = NULL;
		ERROR (parse_state->current_slide_text_count > 0, "Parse Error 2");
		parse_state->current_slide_text_count --;
	}
}

static void
slide_list_with_text_parse_state_finish_slide (PresentPresentation *presentation, SlideListWithTextParseState *parse_state)
{
	slide_list_with_text_parse_state_finish_text (presentation, parse_state);
	if (parse_state->current_slide) {
		present_presentation_append_slide (presentation, parse_state->current_slide);
		g_object_unref (parse_state->current_slide);
		parse_state->current_slide = NULL;
	}
}

#undef ERROR
#define ERROR(conditional, message) if (!(conditional)) { g_warning ("Error: %s", (message)); return; }

#define STACK_TOP GO_MS_PARSER_STACK_TOP(stack)
#define STACK_SECOND GO_MS_PARSER_STACK_SECOND(stack)

static void
handle_atom (GOMSParserRecord *record, GSList *stack, const guint8 *data, GsfInput *input, GError **err, gpointer user_data)
{
	ParseUserData *parse_user_data = user_data;
	switch (record->opcode) {
	case FontEntityAtom:
		if (record->inst >= parse_user_data->fonts->len)
			g_ptr_array_set_size (parse_user_data->fonts, record->inst + 1);
		if (g_ptr_array_index (parse_user_data->fonts, record->inst))
			g_free (g_ptr_array_index (parse_user_data->fonts, record->inst));
		g_ptr_array_index (parse_user_data->fonts, record->inst) = g_utf16_to_utf8 ((gunichar2 *) data, record->length / 2, NULL, NULL, NULL);
		break;
	case TxMasterStyleAtom:
		{
			int indentation_levels;
			int indentation_level;
			guint i = 0;
			GodDefaultAttributes *default_attributes;
			gboolean first = TRUE;

			if (stack && STACK_TOP->opcode == Environment)
				return;

			ERROR (stack && STACK_TOP->opcode == MainMaster, "Placement Error");

			default_attributes = god_default_attributes_new ();

			indentation_levels = GSF_LE_GET_GUINT16 (data);
			i += 2;
			if (record->inst >= 5) {
				i += 2;
				first = FALSE;
			}
			for (indentation_level = 0; indentation_level < indentation_levels; indentation_level ++) {
				GList *pango_attributes = NULL;
				GodParagraphAttributes *para_attributes = god_paragraph_attributes_new ();
				guint32 fields;

				/* Paragraph Attributes */
				fields = GSF_LE_GET_GUINT32 (data + i);
				i += 4;

				/*				g_print ("%d: %x\n", indentation_level, fields);*/
				if (fields & 0x000f) {
					guint16 buflags = GSF_LE_GET_GUINT16 (data + i);
					if (fields & 0x0001) {
						g_object_set (para_attributes, "bullet_on", (gboolean) buflags & 0x1);
					}
					i += 2; /* Bullet Flags */
				}
				if (fields & 0x0080) {
					g_object_set (para_attributes, "bullet_character", (guint) (GSF_LE_GET_GUINT16 (data + i)), NULL);
					i += 2; /* Bullet Char */
				}
				if (fields & 0x0010) {
					guint font_index = GSF_LE_GET_GUINT16 (data + i);
					if (font_index < parse_user_data->fonts->len &&
					    g_ptr_array_index (parse_user_data->fonts, font_index)) {
						g_object_set (para_attributes, "bullet_family", g_ptr_array_index (parse_user_data->fonts, font_index), NULL);
					}
					i += 2; /* Bullet Font */
				}
				if (fields & 0x0040) {
					g_object_set (para_attributes, "bullet_size", (double) GSF_LE_GET_GUINT16 (data + i) / 100.0, NULL);
					i += 2; /* Bullet Height */
				}
				if (fields & 0x0020)
					i += 4; /* Bullet Color */
				if (first ? (fields & 0x0f00) : (fields & 0x0800)) {
					g_object_set (para_attributes, "alignment", (guint) ((GSF_LE_GET_GUINT16 (data + i)) & 3), NULL);
					i += 2; /* Justification last 2 bits */
				}
				if (fields & 0x1000)
					i += 2; /* line feed */
				if (fields & 0x2000) {
					double space_before = 0;
					int space;
					space = GSF_LE_GET_GUINT16 (data + i);
					if (space & 0x8000) {
						space = 0x10000 - space;
					}
					space_before = space * (UN_PER_IN / 576.0);
					g_object_set (para_attributes,
						      "space_before", space_before,
						      NULL);
					i += 2; /* upper dist */
				}
				if (fields & 0x4000) {
					double space_after = 0;
					int space;
					space = GSF_LE_GET_GUINT16 (data + i);
					if (space & 0x8000) {
						space = 0x10000 - space;
					}
					space_after = space * (UN_PER_IN / 576.0);
					g_object_set (para_attributes,
						      "space_after", space_after,
						      NULL);
					i += 2; /* upper dist */
				}
				if (first) {
					if (fields & 0x8000) {
						g_object_set (para_attributes, "indent", (double) (GO_IN_TO_UN ((go_unit_t) GSF_LE_GET_GUINT16 (data + i)) / 576), NULL);
						i += 2; /* Text offset */
						if (i > record->length)
							break;
					}
					if (fields & 0x00010000) {
						g_object_set (para_attributes, "bullet_indent", (double) (GO_IN_TO_UN ((go_unit_t) GSF_LE_GET_GUINT16 (data + i)) / 576), NULL);
						i += 2; /* Bullet offset */
						if (i > record->length)
							break;
					}
					if (fields & 0x00020000) {
						i += 2; /* Default tab */
						if (i > record->length)
							break;
					}
					if (fields & 0x00200000) {
						guint tab_count = GSF_LE_GET_GUINT16 (data + i);
						i += 2 + tab_count * 4; /* Tabs */
						if (i > record->length)
							break;
					}
					if (fields & 0x00040000) {
						i += 2; /* Unknown */
						if (i > record->length)
							break;
					}
					if (fields & 0x00080000) {
						i += 2; /* Asian Line Break */
						if (i > record->length)
							break;
					}
					if (fields & 0x00100000) {
						i += 2; /* bidi */
						if (i > record->length)
							break;
					}
				} else {
					if (fields & 0x8000) {
						i += 2; /* Unknown */
						if (i > record->length)
							break;
					}
					if (fields & 0x0100) {
						g_object_set (para_attributes, "indent", (double) (GO_IN_TO_UN ((go_unit_t) GSF_LE_GET_GUINT16 (data + i)) / 576), NULL);
						i += 2; /* Text offset */
						if (i > record->length)
							break;
					}
					if (fields & 0x0200) {
						i += 2; /* Unknown */
						if (i > record->length)
							break;
					}
					if (fields & 0x0400) {
						g_object_set (para_attributes, "bullet_indent", (double) (GO_IN_TO_UN ((go_unit_t) GSF_LE_GET_GUINT16 (data + i)) / 576), NULL);
						i += 2; /* Bullet offset */
						if (i > record->length)
							break;

					}
					if (fields & 0x00010000) {
						i += 2; /* Unknown */
						if (i > record->length)
							break;
					}
					if (fields & 0x000e0000) {
						i += 2; /* Asian Line Break some bits. */
						if (i > record->length)
							break;
					}
					if (fields & 0x00100000) {
						guint tab_count = GSF_LE_GET_GUINT16 (data + i);
						i += 2 + tab_count * 4; /* Tabs */
						if (i > record->length)
							break;
					}
					if (fields & 0x00200000) {
						i += 2;
						if (i > record->length)
							break;
					}
				}

				/* Character Attributes */
				fields = GSF_LE_GET_GUINT32 (data + i);
				i += 4;
				if (fields & 0x0000ffff)
					i += 2; /* Bit Field */
				if (fields & 0x00010000) {
					guint font_index = GSF_LE_GET_GUINT16 (data + i);
					if (font_index < parse_user_data->fonts->len &&
					    g_ptr_array_index (parse_user_data->fonts, font_index)) {
						pango_attributes = g_list_prepend (pango_attributes,
										   pango_attr_family_new (g_ptr_array_index (parse_user_data->fonts, font_index)));
					}
					i += 2;
				}
				if (fields & 0x00200000)
					i += 2; /* Asian or Complex Font */
				if (fields & 0x00400000)
					i += 2; /* Unknown */
				if (fields & 0x00800000)
					i += 2; /* Symbol */
				if (fields & 0x00020000) {
					pango_attributes = g_list_prepend (pango_attributes,
									   pango_attr_size_new (GSF_LE_GET_GUINT16 (data + i) * PANGO_SCALE * 72 / 96)); /* Powerpoint fonts are specified in pixels at 96 dpi. */
					i += 2;
				}
				if (fields & 0x00040000)
					i += 4; /* Font Color */
				if (fields & 0x00080000)
					i += 2; /* Escapement */
				if (fields & 0x00100000)
					i += 2; /* Unknown */

				god_default_attributes_set_paragraph_attributes_for_indent (default_attributes,
											    indentation_level,
											    para_attributes);
				god_default_attributes_set_pango_attributes_for_indent (default_attributes,
											indentation_level,
											pango_attributes);
				g_list_foreach (pango_attributes, (GFunc) pango_attribute_destroy, NULL);
				g_list_free (pango_attributes);
				g_object_unref (para_attributes);

				first = FALSE;
			}
			present_presentation_set_default_attributes_for_text_type (parse_user_data->presentation,
										   record->inst,
										   default_attributes);
			g_object_unref (default_attributes);
		}
		break;
	case DocumentAtom:
		{
			GodAnchor *anchor;
			GoRect rect;

			ERROR (stack && STACK_TOP->opcode == Document, "Placement Error");

			ERROR (record->length == 40, "Incorrect DocumentAtom");

			rect.top = 0;
			rect.left = 0;
			rect.right = GO_IN_TO_UN ((go_unit_t)GSF_LE_GET_GUINT32 (data)) / 576;
			rect.bottom = GO_IN_TO_UN ((go_unit_t)GSF_LE_GET_GUINT32 (data + 4)) / 576;

			anchor = god_anchor_new ();
			god_anchor_set_rect (anchor, &rect);
			present_presentation_set_extents (parse_user_data->presentation, anchor);

			rect.right = GO_IN_TO_UN ((go_unit_t)GSF_LE_GET_GUINT32 (data + 8)) / 576;
			rect.bottom = GO_IN_TO_UN ((go_unit_t)GSF_LE_GET_GUINT32 (data + 12)) / 576;

			anchor = god_anchor_new ();
			god_anchor_set_rect (anchor, &rect);
			present_presentation_set_notes_extents (parse_user_data->presentation, anchor);
		}
		break;
	case SlidePersistAtom:
		{
			SlideListWithTextParseState *parse_state;
			ERROR (stack && STACK_TOP->opcode == SlideListWithText, "Placement Error");

			parse_state = stack ? STACK_TOP->parse_state : NULL;
			if (parse_state) {
				slide_list_with_text_parse_state_finish_slide (parse_user_data->presentation, parse_state);
				parse_state->current_slide = present_slide_new();
				parse_state->current_slide_text_count = GSF_LE_GET_GUINT32 (data + 8);
			}
		}
		break;
	case TextHeaderAtom:
		{
			SlideListWithTextParseState *parse_state;
			ERROR (stack && STACK_TOP->opcode == SlideListWithText, "Placement Error");

			parse_state = stack ? STACK_TOP->parse_state : NULL;
			if (parse_state) {
				slide_list_with_text_parse_state_finish_text (parse_user_data->presentation, parse_state);
				parse_state->current_text = PRESENT_TEXT (present_text_new (record->inst, GSF_LE_GET_GUINT32(data)));
				g_object_set (parse_state->current_text,
					      "presentation", parse_user_data->presentation,
					      NULL);
			}
		}
		break;
	case TextCharsAtom:
		{
			SlideListWithTextParseState *parse_state;
			ERROR (stack && STACK_TOP->opcode == SlideListWithText, "Placement Error");

			parse_state = stack ? STACK_TOP->parse_state : NULL;
			if (parse_state) {
				char *text = g_utf16_to_utf8 ((gunichar2 *) data, record->length / 2, NULL, NULL, NULL);
				god_text_model_set_text (GOD_TEXT_MODEL (parse_state->current_text), text);
				g_free (text);
			}
		}
		break;
	case TextBytesAtom:
		{
			SlideListWithTextParseState *parse_state;
			ERROR (stack && STACK_TOP->opcode == SlideListWithText, "Placement Error");

			parse_state = stack ? STACK_TOP->parse_state : NULL;
			if (parse_state) {
				char *text = g_convert (data, record->length, "utf8", "latin1", NULL, NULL, NULL);
				god_text_model_set_text (GOD_TEXT_MODEL (parse_state->current_text), text);
				g_free (text);
			}
		}
		break;
	case StyleTextPropAtom:
		{
			SlideListWithTextParseState *parse_state;
			ERROR (stack && STACK_TOP->opcode == SlideListWithText, "Placement Error");
			parse_state = stack ? STACK_TOP->parse_state : NULL;
			if (parse_state) {
				ppt_parsing_helper_parse_style_text_prop_atom (data, record->length, GOD_TEXT_MODEL (parse_state->current_text), parse_user_data->fonts);
			}
		}
		break;
	case PPDrawingGroup:
		{
			GodDrawingGroup *drawing_group;
			ERROR (present_presentation_get_drawing_group (parse_user_data->presentation) == NULL, "Multiple Drawing Groups");
			drawing_group = god_drawing_group_read_ms (input, record->length, NULL, NULL);
			ERROR (drawing_group, "DrawingGroup load failed");
			present_presentation_set_drawing_group (parse_user_data->presentation,
								drawing_group);
			g_object_unref (drawing_group);
		}
		break;
	case PPDrawing:
		{
			GodDrawingMsClientHandler *handler;
			GodDrawing *drawing;

			ERROR (stack && (STACK_TOP->opcode == Slide ||
					 STACK_TOP->opcode == MainMaster ||
					 STACK_TOP->opcode == Notes), "Placement Error");

			if (STACK_TOP->opcode == Slide) {
				SlideParseState *parse_state = STACK_TOP->parse_state;

				handler = god_drawing_ms_client_handler_ppt_new (parse_state->slide, parse_user_data->fonts);
			} else {
				handler = god_drawing_ms_client_handler_ppt_new (NULL, parse_user_data->fonts);
			}

			drawing = god_drawing_read_ms (input, record->length, handler, NULL);
			ERROR (drawing, "Drawing load failed");
			god_drawing_set_drawing_group (drawing,
						       present_presentation_get_drawing_group (parse_user_data->presentation));
			g_object_unref (handler);
			if (STACK_TOP->opcode == Slide) {
				SlideParseState *parse_state = STACK_TOP->parse_state;
				present_slide_set_drawing (parse_state->slide,
							   drawing);
			}
			g_object_unref (drawing);
		}
		break;
	}
}

static void
start_container (GSList *stack, GsfInput *input, GError **err, gpointer user_data)
{
	ParseUserData *parse_user_data = user_data;
	switch (STACK_TOP->opcode) {
	case SlideListWithText:
		if (STACK_TOP->inst == 0) {
			SlideListWithTextParseState *parse_state = g_new0 (SlideListWithTextParseState, 1);
			STACK_TOP->parse_state = parse_state;
		}
		break;
	case Slide:
		{
			SlideParseState *parse_state = g_new0 (SlideParseState, 1);
			parse_state->slide = present_presentation_get_slide (parse_user_data->presentation,
									     parse_user_data->slides_read ++);
			STACK_TOP->parse_state = parse_state;
		}
		break;
	}
}


static void
end_container (GSList *stack, GsfInput *input, GError **err, gpointer user_data)
{
	ParseUserData *parse_user_data = user_data;
	switch (STACK_TOP->opcode) {
	case SlideListWithText:
		{
			SlideListWithTextParseState *parse_state;
			parse_state = STACK_TOP->parse_state;
			if (parse_state) {
				slide_list_with_text_parse_state_finish_slide (parse_user_data->presentation, parse_state);
				g_free (parse_state);
			}
		}
		break;
	default:
		break;
	}
}


static GOMSParserCallbacks callbacks = { handle_atom,
					 start_container,
					 end_container };


static PresentPresentation *
parse_stream (GsfInput *input, guint length)
{
	ParseUserData user_data;

	user_data.presentation = present_presentation_new ();
	user_data.slides_read = 0;
	user_data.fonts = g_ptr_array_new();

	go_ms_parser_read (input,
			   length,
			   types,
			   (sizeof (types) / sizeof (types[0])),
			   &callbacks,
			   &user_data,
			   NULL);

	g_ptr_array_foreach (user_data.fonts, (GFunc) g_free, NULL);
	g_ptr_array_free (user_data.fonts, TRUE);

	return user_data.presentation;
}

PresentPresentation *
load_ppt (char *input_file)
{
	GsfInput  *input, *stream;
	GsfInfile *infile;
	GError    *err = NULL;
	PresentPresentation *presentation = NULL;

	input = GSF_INPUT (gsf_input_mmap_new (input_file, &err));
	if (input == NULL) {
		g_return_val_if_fail (err != NULL, NULL);
		g_warning ("'%s' error: %s", input_file, err->message);
		g_error_free (err);
		return NULL;
	}

	input = GSF_INPUT (gsf_input_uncompress (input));

	infile = GSF_INFILE (gsf_infile_msole_new (input, &err));
	if (infile == NULL) {

		g_return_val_if_fail (err != NULL, NULL);

		g_warning ("'%s' Not an OLE file: %s", input_file, err->message);
		g_error_free (err);
		g_object_unref (G_OBJECT (input));
		return NULL;
	}

	stream = gsf_infile_child_by_name (infile, "PowerPoint Document");

	if (stream != NULL) {
		presentation = parse_stream (stream, gsf_input_remaining (stream));
		g_object_unref (G_OBJECT (stream));
	}

	if (presentation) {
		GodDrawingGroup *drawing_group = present_presentation_get_drawing_group (presentation);
		if (drawing_group) {
			stream = gsf_infile_child_by_name (infile, "Pictures");

			if (stream != NULL) {
				god_drawing_group_parse_images (drawing_group,
								stream,
								gsf_input_remaining (stream),
								NULL,
								NULL);
				g_object_unref (G_OBJECT (stream));
			}
		}
		g_object_unref (drawing_group);
	}
	g_object_unref (G_OBJECT (infile));
	g_object_unref (G_OBJECT (input));

	return presentation;
}


syntax highlighted by Code2HTML, v. 0.9.1