/* AbiWord * Copyright (C) 2000 AbiSource, Inc. * Copyright (C) 2000, 2001, 2004 Frodo Looijaard * * This program 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; either version 2 * of the License, or (at your option) any later version. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /* This exporter is written by Frodo Looijaard */ // Export Word or TextEd data from a Psion file. // We use libpsiconv for the real work // To do: // Add support for bullets // Better determination of font type (serif, sansserif or nonproportional) // Add support for other page-level layout // Add support for objects, fields, format marks // Search for TODO for more things to do. #include "ie_exp_Psion.h" #include "ut_string.h" #include "ut_stringbuf.h" #include "ut_bytebuf.h" #include "ut_units.h" #include "ut_debugmsg.h" #include "ut_wctomb.h" #include "ut_assert.h" #include "pt_Types.h" #include "pd_Document.h" #include "pp_AttrProp.h" #include "px_ChangeRecord.h" #include "px_CR_Object.h" #include "px_CR_Span.h" #include "px_CR_Strux.h" #include "pd_Style.h" #include "fd_Field.h" #include "png.h" #include /********************* * Local definitions * *********************/ /*! * A ByteBuf with current location structure * * This is a very ugly and hackish implementation, copied from ut_png.cpp. * The idea is we need a ByteBuf which can remember where we have last read * it. This is used for the libpng stuff, which is itself quite ugly. */ struct _bb { const UT_ByteBuf* pBB; UT_uint32 iCurPos; }; /*********************** * Auxiliary functions * ***********************/ /*! * Convert a AbiWord UTF8 string to a Psiconv UCS2 string. * * Returns NULL on error. */ static psiconv_ucs2 *utf8_to_ucs2(const gchar *input) { UT_uint32 read=0,written=0; UT_uint32 i; char *intermediate; psiconv_ucs2 *result; if (!input) return NULL; read=written=0; intermediate = UT_convert((char *)input,strlen(input) * sizeof(*input), "UTF-8","UCS-2",&read,&written); if (!(result = (psiconv_ucs2 *) malloc(sizeof(*result) * (written / 2 + 1)))) { free(intermediate); return NULL; } for (i = 0; i < written/2; i++) result[i] = intermediate[i*2] + (intermediate[i*2+1] << 8); result[i] = 0; free(intermediate); return result; } /*! * Translate a hex digit character token to its decimal value * */ static int hexDigitToDec(char in) { switch(in) { case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': return 10; case 'b': return 11; case 'c': return 12; case 'd': return 13; case 'e': return 14; case 'f': return 15; default: return 0; } } /*! * Parse a color value * * The color is in the input parameter, in AbiWord format. * Example formats: 000000 ffffff ab6e10 */ static void parseColor(const char *input,psiconv_color color) { color->red = hexDigitToDec(input[0])*16+hexDigitToDec(input[1]); color->green = hexDigitToDec(input[2])*16+hexDigitToDec(input[3]); color->blue = hexDigitToDec(input[4])*16+hexDigitToDec(input[5]); } /*! * Parse a single tab (clobbering the input!) * * The tab is in the input parameter, in AbiWord format * Example formats: 7.315cm/C 6.4322in/R * Note that the input string is changed for efficiency reasons. As this * function is only meant to be called from parseTabs, that is no problem. */ static void parseTab(char *input,psiconv_tab tab) { char *slash; slash = strchr(input,'/'); tab->kind = psiconv_tab_left; if (slash) { if (*(slash+1) == 'R') tab->kind = psiconv_tab_right; else if (*(slash+1) == 'C') tab->kind = psiconv_tab_centre; *slash = '\000'; } tab->location = (float)UT_convertToDimension(input,DIM_CM); } /*! * Parse all tabs * * The tabs are in the input parameter, in AbiWord format. * Example format: 7.315cm/C,14.641cm/R */ static bool parseTabs(const char *input,psiconv_tab_list tabs) { const char *currentPos; const char *nextPos; char *copy; struct psiconv_tab_s tab; currentPos = input; while (*currentPos != '\000') { // Determine where this tab ends in the input nextPos = strchr(currentPos,','); if (!nextPos) nextPos = strchr(currentPos,'\000'); // Copy the current tab value to a newly allocated variable if (!(copy = (char *) malloc(nextPos - currentPos + 1))) return false; memcpy(copy,currentPos,nextPos-currentPos); copy[nextPos - currentPos] = '\000'; // Parse the tab parseTab(copy,&tab); // Free the allocated variable free(copy); // Add the psiconv tab to the list if (psiconv_list_add(tabs,&tab)) return false; // Skip tab seperators currentPos = nextPos; while ((*currentPos == ',' || *currentPos == ' ')) currentPos++; } return true; } /*! * Parse a font */ static bool parseFont(const char *fontname,psiconv_font font) { psiconv_ucs2 *tempstr; const psiconv_ucs2 text_Courier[] = {'C','o','u','r','i','e','r',0 }; const psiconv_ucs2 text_Mono[] = {'M','o','n','o',0 }; const psiconv_ucs2 text_Arial[] = {'A','r','i','a','l',0 }; const psiconv_ucs2 text_Goth[] = {'G','o','t','h',0 }; const psiconv_ucs2 text_Helvetic[] = {'H','e','l','v','e','t','i','c',0 }; const psiconv_ucs2 text_Univers[] = {'U','n','i','v','e','r','s',0 }; const psiconv_ucs2 text_Sans[] = {'S','a','n','s',0 }; // If utf8_to_ucs2 fails, we still need to have the default fontname around // to reassign it. And if it succeeds, we need to free it. Hence the // juggling around with tempstr. tempstr = font->name; if (!(font->name = utf8_to_ucs2(fontname))) { font->name = tempstr; return false; } free(tempstr); // There may be a good general way to do this, but I do // not know how. We need to determine whether we have a proportional // serifed font, a proportional non-serifed font, or a non-proportional // font. if (psiconv_unicode_strstr(font->name,text_Courier) || psiconv_unicode_strstr(font->name,text_Mono)) font->screenfont = psiconv_font_nonprop; else if (psiconv_unicode_strstr(font->name,text_Arial) || psiconv_unicode_strstr(font->name,text_Goth) || psiconv_unicode_strstr(font->name,text_Helvetic) || psiconv_unicode_strstr(font->name,text_Univers) || psiconv_unicode_strstr(font->name,text_Sans)) font->screenfont = psiconv_font_sansserif; else font->screenfont = psiconv_font_serif; return true; } /*! * Update all character layout with the properties pointed to. */ static bool updateCharacterLayout(const PP_AttrProp *pAP, psiconv_character_layout layout) { const gchar* szValue; // Font name if (pAP->getProperty("font-family",szValue)) if (!parseFont(szValue,layout->font)) return false; // Font size if (pAP->getProperty("font-size",szValue)) layout->font_size = (float)UT_convertToDimension((const char *) szValue,DIM_PT); // Bold if (pAP->getProperty("font-weight",szValue)) { if (!strcmp((const char *) szValue,"bold")) layout->bold = psiconv_bool_true; else layout->bold = psiconv_bool_false; } // Italic if (pAP->getProperty("font-style",szValue)) { if (!strcmp((const char *) szValue,"italic")) layout->italic = psiconv_bool_true; else layout->italic = psiconv_bool_false; } // Underline and line-through are collected under text-decoration // (AbiWord really looked too much to CSS). For the Psion, they are // two independent settings. if (pAP->getProperty("text-decoration",szValue)) { if (strstr((const char *) szValue,"underline")) layout->underline = psiconv_bool_true; else layout->underline = psiconv_bool_false; if (strstr((const char *) szValue,"line-through")) layout->strikethrough = psiconv_bool_true; else layout->strikethrough = psiconv_bool_false; } // Superscript and subscript if (pAP->getProperty("text-position",szValue)) { if (!strcmp((const char *) szValue,"superscript")) layout->super_sub = psiconv_superscript; else if (!strcmp((const char *) szValue,"subscript")) layout->super_sub = psiconv_subscript; else layout->super_sub = psiconv_normalscript; } // Text color if (pAP->getProperty("color",szValue)) parseColor((char *) szValue,layout->color); // Background color if (pAP->getProperty("bgcolor",szValue)) parseColor((char *) szValue,layout->back_color); return true; } /*! * Update all paragraph layout with the properties pointed to. */ static bool updateParagraphLayout(const PP_AttrProp *pAP, psiconv_paragraph_layout layout) { const gchar* szValue; bool widowsorphans; // Indentation left, right and first line. if (pAP->getProperty("margin-left",szValue)) layout->indent_left = (psiconv_length_t) UT_convertToDimension((const char *) szValue, DIM_CM); if (pAP->getProperty("margin-right",szValue)) layout->indent_right = (psiconv_length_t) UT_convertToDimension((const char *) szValue, DIM_CM); if (pAP->getProperty("text-indent",szValue)) layout->indent_first = (psiconv_length_t) UT_convertToDimension((const char *) szValue, DIM_CM); // Text justification if (pAP->getProperty("text-align",szValue)) { if (!strcmp((const char *) szValue,"center")) layout->justify_hor = psiconv_justify_centre; else if (!strcmp((const char *) szValue,"right")) layout->justify_hor = psiconv_justify_right; else if (!strcmp((const char *) szValue,"justify")) layout->justify_hor = psiconv_justify_full; else layout->justify_hor = psiconv_justify_left; } #if 0 char *tempstr; // Muchos trouble. Forget about it for now. if (pAP->getProperty("line-height",szValue)) { layout->linespacing_exact = szValue[strlen((char *)szValue)-1]=='+'?psiconv_bool_false: psiconv_bool_true; tempstr = g_strdup((char *)szValue); if (!layout->linespacing_exact) tempstr[strlen((char *)szValue)-1] = '\000'; layout->linespacing = (psiconv_size_t)UT_convertToDimension(tempstr,DIM_PT); free(tempstr); } #endif // Space above and below paragraphs if (pAP->getProperty("margin-top",szValue)) layout->space_above = (psiconv_size_t)UT_convertToDimension((const char *) szValue, DIM_PT); if (pAP->getProperty("margin-bottom",szValue)) layout->space_below = (psiconv_size_t)UT_convertToDimension((const char *) szValue, DIM_PT); // Text flow on page breaks: keep paragraph on one page, keep paragraph // with next paragraph on one page, protect agains widows and orphans. if (pAP->getProperty("keep-together",szValue)) { if (!strcmp((const char *) szValue,"yes")) layout->keep_together = psiconv_bool_true; else layout->keep_together = psiconv_bool_false; } if (pAP->getProperty("keep-with-next",szValue)) { if (!strcmp((const char *) szValue,"yes")) layout->keep_with_next = psiconv_bool_true; else layout->keep_with_next = psiconv_bool_false; } // Set widowsorphans if either widows or orphans is set and // unequal to "0". widowsorphans = false; if (pAP->getProperty("widows",szValue)) widowsorphans |= (strcmp((const char *) szValue,"0") != 0); if (pAP->getProperty("orphans",szValue)) widowsorphans |= (strcmp((const char *) szValue,"0") != 0); layout->no_widow_protection = widowsorphans?psiconv_bool_false:psiconv_bool_true; // Tabs. We have both a default tab interval, and specific tab stops here. if (pAP->getProperty("default-tab-interval",szValue)) layout->tabs->normal = (psiconv_length_t)UT_convertToDimension((const char *) szValue, DIM_CM); if (pAP->getProperty("tabstops",szValue)) if (!parseTabs((char *)szValue,layout->tabs->extras)) return false; // TODO: Bullets return true; } /*! * Auxiliary function for use with libpng: read data from a ByteBuf. * * This function reads data for use with libpng. The data was put into a * ByteBuf. Libpng does not tell us what data we have already read, so we * must remember that ourselves. That is why we use the ultra-ugly _bb. */ static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length) { struct _bb* p = (struct _bb*) png_get_io_ptr(png_ptr); UT_DEBUGMSG(("PSION: read_png_data: %d bytes at %d\n",length,p->iCurPos)); const UT_Byte* pBytes = p->pBB->getPointer(0); memcpy(data, pBytes + p->iCurPos, length); p->iCurPos += length; } /*********************************** * class IE_Exp_Psion_Word_Sniffer * ***********************************/ /*! * Look at the extension to guess whether this is a Psion Word file. * * Actually, the Psion itself does not use extensions (much), so I just * made up my own convention (.psiword) here. It's better than nothing. */ bool IE_Exp_Psion_Word_Sniffer::recognizeSuffix(const char * szSuffix) { return (!g_ascii_strcasecmp(szSuffix,".psiword")); } /*! * Create an IE_Exp_Psion_Word object */ UT_Error IE_Exp_Psion_Word_Sniffer::constructExporter(PD_Document * pDocument, IE_Exp ** ppie) { IE_Exp_Psion_Word *p = new IE_Exp_Psion_Word(pDocument); *ppie = p; return UT_OK; } /*! * Some import filter settings. We use the .psiword extension */ bool IE_Exp_Psion_Word_Sniffer::getDlgLabels(const char ** pszDesc, const char ** pszSuffixList, IEFileType * ft) { *pszDesc = "Psion Word (.psiword)"; *pszSuffixList = "*.psiword"; *ft = getFileType(); return true; } /************************************* * class IE_Exp_Psion_TextEd_Sniffer * *************************************/ /*! * Look at the extension to guess whether this is a Psion TextEd file. * * Actually, the Psion itself does not use extensions (much), so I just * made up my own convention (.psitext) here. It's better than nothing. */ bool IE_Exp_Psion_TextEd_Sniffer::recognizeSuffix(const char * szSuffix) { return (!g_ascii_strcasecmp(szSuffix,".psitext")); } /*! * Create an IE_Exp_Psion_TextEd object */ UT_Error IE_Exp_Psion_TextEd_Sniffer::constructExporter(PD_Document * pDocument, IE_Exp ** ppie) { IE_Exp_Psion_TextEd * p = new IE_Exp_Psion_TextEd(pDocument); *ppie = p; return UT_OK; } /*! * Some import filter settings. We use the .psitext extension */ bool IE_Exp_Psion_TextEd_Sniffer::getDlgLabels(const char ** pszDesc, const char ** pszSuffixList, IEFileType * ft) { *pszDesc = "Psion TextEd (.psitext)"; *pszSuffixList = "*.psitext"; *ft = getFileType(); return true; } /********************** * class IE_Exp_Psion * **********************/ /*! * Write a Psion document to file. * * This function writes a document to file. To do this, we must first * construct a psiconv_file (a sort of abstract syntax tree of the document) * then translate it to a psiconv_buffer (the file in Psion format), and * finally write the contents of this buffer to a file. * As most of the work is the same for any type of Psion file, we define * this method here in the base exporter class. The only thing that is * really different between TextEd and Word files is the psiconv_file; * so we construct it through the virtual method _createPsionFile. */ UT_Error IE_Exp_Psion::_writeDocument(void) { const int MAXBUFLEN = 512; char buf[MAXBUFLEN]; char *pByte; unsigned int i; int iRes; PL_Psion_Listener *listener; psiconv_file psionfile; psiconv_buffer psiondump; psiconv_config config; // psiconv_text_and_layout paragraphs; // psiconv_word_style_list styles; // A whole cascade of commands, with only one purpose: // create a proper listener, signal it to traverse the document and // let it create a complete Psion file. if (!(listener = _constructListener()) || !listener->startDocument() || !getDoc()->tellListener(listener) || !listener->finishDocument() || !(psionfile = listener->createPsionFile())) { delete listener; return UT_IE_COULDNOTWRITE; } delete listener; // Initialize a Psiconv config structure config = psiconv_config_default(); if (!config) return UT_IE_NOMEMORY; config->error_handler = &psion_error_handler; psiconv_config_read(NULL,&config); // Write the file to a buffer (psiondump), and free all allocated data iRes = psiconv_write(config,&psiondump,psionfile); psiconv_free_file(psionfile); psiconv_config_free(config); if (iRes) return UT_IE_COULDNOTWRITE; // Write the content of psiondump to file. We do this in // MAXBUFFERLEN chunks, because it would be horribly slow if we did it // byte by byte. Note that we can't just access the psiconv_buffer, // we have to read it byte by byte. for (i = 0; i < psiconv_buffer_length(psiondump); i++) { if (!(pByte = (char *) psiconv_buffer_get(psiondump,i))) { UT_ASSERT(UT_SHOULD_NOT_HAPPEN); psiconv_buffer_free(psiondump); return UT_IE_COULDNOTWRITE; } buf[i % MAXBUFLEN] = *pByte; if ((i % MAXBUFLEN == MAXBUFLEN-1) || (i == psiconv_buffer_length(psiondump) - 1)) write(buf,(i % MAXBUFLEN)+1); if (m_error) { psiconv_buffer_free(psiondump); return UT_IE_COULDNOTWRITE; } } // We are finished. Free the buffer and return happily. psiconv_buffer_free(psiondump); return UT_OK; } /************************** * class PL_Psion_Listener * **************************/ /*! Constructor * * The listener uses several psiconv_file fragments while building * paragraphs (and perhaps other things). We can't allocate them * here, because we can't fail in a constructor. * At all places, we use NULL to indicate an unallocated structure. */ PL_Psion_Listener::PL_Psion_Listener(PD_Document * pDocument): m_pDocument(pDocument), m_paragraphs(NULL),m_styles(NULL), m_header(NULL),m_footer(NULL), m_inParagraph(false), m_sectionType(section_none), m_currentParagraphText(NULL), m_currentParagraphPLayout(NULL), m_currentParagraphCLayout(NULL), m_currentParagraphInLines(NULL), m_currentParagraphStyle(0) { } /*! * Destructor * * Deallocate those psiconv_file fragments that are currently allocated. * NULL is used to indicate an unallocated structure. */ PL_Psion_Listener::~PL_Psion_Listener(void) { if (m_currentParagraphPLayout) psiconv_free_paragraph_layout(m_currentParagraphPLayout); if (m_currentParagraphCLayout) psiconv_free_character_layout(m_currentParagraphCLayout); if (m_currentParagraphInLines) psiconv_list_free(m_currentParagraphInLines); if (m_currentParagraphText) psiconv_list_free(m_currentParagraphText); if (m_paragraphs) psiconv_list_free(m_paragraphs); if (m_header) psiconv_free_page_header(m_header); if (m_footer) psiconv_free_page_header(m_footer); } /*! * Initialize a new Psion Listener. * * Call this right after the constructor. * We can't call it from the constructor, because our constructor may not fail. * We initialize all kinds of private variables. Some juggling is done with * NULL values to make sure everything can be safely deallocated at any time * by our destructor. * We also parse all styles here. This is needed further on, and listeners * do not traverse styles. */ bool PL_Psion_Listener::startDocument(void) { // Allocate list of characters. This list will contain Psiconv ucs2 // characters. It will only be freed in the destructor; use // psiconv_list_empty if you want to clear it. if (!(m_currentParagraphText = psiconv_list_new(sizeof(psiconv_ucs2)))) return false; // Allocate the main text, which is a list of Psiconv paragraphs. if (!(m_paragraphs = psiconv_list_new(sizeof(struct psiconv_paragraph_s)))) return false; // Allocate default header with no text if (!(m_header = (psiconv_page_header) malloc(sizeof(*m_header)))) return false; m_header->on_first_page = psiconv_bool_true; m_header->base_paragraph_layout = NULL; m_header->base_character_layout = NULL; m_header->text = NULL; if (!(m_header->base_paragraph_layout = psiconv_basic_paragraph_layout())) return false; if (!(m_header->base_character_layout = psiconv_basic_character_layout())) return false; if (!(m_header->text = (psiconv_texted_section) malloc(sizeof(*m_header->text)))) return false; m_header->text->paragraphs = NULL; if (!(m_header->text->paragraphs = psiconv_list_new(sizeof(struct psiconv_paragraph_s)))) return false; // Allocate default footer with no text if (!(m_footer = (psiconv_page_header) malloc(sizeof(*m_footer)))) return false; m_footer->on_first_page = psiconv_bool_true; m_footer->base_paragraph_layout = NULL; m_footer->base_character_layout = NULL; m_footer->text = NULL; if (!(m_footer->base_paragraph_layout = psiconv_basic_paragraph_layout())) return false; if (!(m_footer->base_character_layout = psiconv_basic_character_layout())) return false; if (!(m_footer->text = (psiconv_texted_section) malloc(sizeof(*m_footer->text)))) return false; m_footer->text->paragraphs = NULL; if (!(m_footer->text->paragraphs = psiconv_list_new(sizeof(struct psiconv_paragraph_s)))) return false; // Parse all styles if (!(_processStyles())) return false; return true; } /*! * Finish the traversing of the document by the Listener. * * Call this after calling tellListener to traverse the document. * Closes remaining paragraphs and sections. */ bool PL_Psion_Listener::finishDocument(void) { return _closeParagraph(); } /*! Standard listener callback for in-block data * * This method is called when a span, object or format mark is found in the * document while traversing it. */ bool PL_Psion_Listener::populate(PL_StruxFmtHandle, const PX_ChangeRecord * pcr) { PT_BufIndex bi; const PX_ChangeRecord_Span * pcrs = NULL; const PX_ChangeRecord_Object * pcro = NULL; const fd_Field *field = NULL; UT_uint32 textlen; const PT_AttrPropIndex api = pcr->getIndexAP(); switch(pcr->getType()) { case PX_ChangeRecord::PXT_InsertSpan: // A text with its layout. We first append the text to the // current paragraph, and afterwards its layout. Note that we // can safely do the static_cast. pcrs = static_cast (pcr); bi = pcrs->getBufIndex(); if (! _writeText(m_pDocument->getPointer(bi),pcrs->getLength(), textlen)) return false; return _addInLine(api,textlen); break; case PX_ChangeRecord::PXT_InsertObject: pcro = static_cast (pcr); switch (pcro->getObjectType()) { case PTO_Image: return _insertImage(api); case PTO_Field: field = pcro->getField(); if (field->getFieldType() == fd_Field::FD_ListLabel) { // Ugly as hell. We have found a list label, so we // need to make a bulleted paragraph here. if (m_inParagraph) m_currentParagraphPLayout->bullet->on = psiconv_bool_true; else { UT_DEBUGMSG(("PSION: List label found outside paragraph (ignored)\n")); } } else { UT_DEBUGMSG(("PSION: Field found (ignored)\n")); } return true; case PTO_Hyperlink: case PTO_Bookmark: default: UT_DEBUGMSG(("PSION: Unknown or unsupported type of object found (ignored)\n")); return true; } case PX_ChangeRecord::PXT_InsertFmtMark: // Format marks are not supported at the moment: ignore UT_DEBUGMSG(("PSION: Insert Format Mark (ignored)\n")); return true; default: // We should have covered all possibilities. UT_ASSERT(0); return false; } } /*! Standard listener callback for block and section data * * This method is called whenever a block (paragraph) or section is found in * the document while traversing it. */ bool PL_Psion_Listener::populateStrux(PL_StruxDocHandle /*sdh*/, const PX_ChangeRecord * pcr, PL_StruxFmtHandle * /*psfh*/) { UT_ASSERT(pcr->getType() == PX_ChangeRecord::PXT_InsertStrux); const PX_ChangeRecord_Strux * pcrx = static_cast (pcr); PT_AttrPropIndex api; const PP_AttrProp * pAP = NULL; const gchar *sectiontype; switch (pcrx->getStruxType()) { case PTX_Block: // Start of a new paragraph. We start a new paragraph, setting // all paragraph-level layouts. if (!_openParagraph(pcr->getIndexAP())) return false; return true; break; case PTX_Section: // A new (main) section. If there is more than one, they are // just appended. We first close the current paragraph, because // belongs to the current section. No harm if no paragraph is open. if (!_closeParagraph()) return false; m_sectionType = section_main; UT_DEBUGMSG(("PSION: New main section\n")); return true; break; case PTX_SectionHdrFtr: // A new header or footer section. If there is more than one, // they are just appended (not good, but it will have to do for // now). We first close the current paragraph, because // belongs to the current section. No harm if no paragraph is open. // We have to do some work to determine whether this is a header // or a footer. // Note that if the Psion file format had some more possibilities, // we good associate sections with their headers/footers like // AbiWord does. Regrettably, a Psion file can only have one // header and one footer, and one main section. if (!_closeParagraph()) return false; if ((api = pcr->getIndexAP()) && m_pDocument->getAttrProp(api,&pAP) && pAP && (pAP->getAttribute("type",sectiontype) && sectiontype)) { if (!strcmp(sectiontype,"header")) { UT_DEBUGMSG(("PSION: New header section\n")); m_sectionType = section_header; } else if (!strcmp(sectiontype,"footer")) { UT_DEBUGMSG(("PSION: New footer section\n")); m_sectionType = section_footer; } else { UT_DEBUGMSG(("PSION: Unknown section type (%s), ignored \n", sectiontype)); m_sectionType = section_none; } } else return false; return true; break; case PTX_SectionTable: case PTX_EndTable: case PTX_SectionCell: case PTX_EndCell: // We do not support them, but there is no harm in them. return true; case PTX_EndFrame: case PTX_EndMarginnote: case PTX_EndFootnote: case PTX_SectionFrame: case PTX_SectionMarginnote: case PTX_SectionFootnote: case PTX_EndEndnote: default: // Things that should never happen. UT_ASSERT_NOT_REACHED(); return false; } } /*! * Add some AbiWord document text to the current paragraph * * Add some text to the current paragraph. Returns the length of the * written text in outLength; this may be more or less than inLength, * because some input characters may need to be represented by more than one * output character, and because we may ignore some input characters. */ bool PL_Psion_Listener::_writeText(const UT_UCSChar *p, UT_uint32 inLength, UT_uint32 &outLength) { UT_uint32 i; psiconv_ucs2 character; #ifdef DEBUG UT_UTF8Stringbuf buffer; buffer.appendUCS4(p,inLength); UT_DEBUGMSG(("PSION: text `%s'\n",buffer.data())); #endif // Append all characters one by one to the current paragraph outLength = 0; for (i = 0; i < inLength; i++) { if (p[i] == UCS_ABI_OBJECT) continue; else if (p[i] == UCS_TAB) character = 0x09; else if ((p[i] == UCS_LF) || (p[i] == UCS_CR)) character = 0x07; else if (p[i] == UCS_FF) character = 0x08; else if ((p[i] == UCS_EN_SPACE) || (p[i] == UCS_EM_SPACE)) character = 0x0f; // I am unsure whether this is right! else if ((p[i] == UCS_EN_DASH) || (p[i] == UCS_EM_DASH)) character = 0x0b; else if (p[i] < 0x20) continue; else if (p[i] > 0xffff) continue; else character = p[i]; if (psiconv_list_add(m_currentParagraphText,&character)) return false; outLength ++; } return true; } /*! * Open a new paragraph, and set paragraph-level layout. * */ bool PL_Psion_Listener::_openParagraph(const PT_AttrPropIndex api) { const PP_AttrProp * pAP = NULL; const gchar *stylename; psiconv_string_t stylename_ucs2 = NULL; psiconv_word_style style; int stylenr; // Close the last paragraph, in case it is still open. _closeParagraph(); UT_DEBUGMSG(("PSION: Open paragraph\n")); // Get all attributes. if (!m_pDocument->getAttrProp(api,&pAP) || !pAP) goto ERROR1; // Set the base layout of this paragraph. If we can find its style, // we use it. If not, we use a Psion default. Note that a style can // contain both paragraph-level and character-level settings, even though // it is a paragraph style. if (pAP->getAttribute("style",stylename) && stylename && (stylename_ucs2 = utf8_to_ucs2(stylename)) && !psiconv_find_style(m_styles,stylename_ucs2,&stylenr) && (style = psiconv_get_style(m_styles,stylenr))) { if (!(m_currentParagraphPLayout = psiconv_clone_paragraph_layout(style->paragraph))) goto ERROR1; if (!(m_currentParagraphCLayout = psiconv_clone_character_layout(style->character))) goto ERROR2; m_currentParagraphStyle = stylenr; } else { if (!(m_currentParagraphPLayout = psiconv_basic_paragraph_layout())) goto ERROR1; if (!(m_currentParagraphCLayout = psiconv_basic_character_layout())) goto ERROR2; m_currentParagraphStyle = 0; } if (stylename_ucs2) { free(stylename_ucs2); stylename_ucs2 = NULL; } // New paragraph, new text. psiconv_list_empty(m_currentParagraphText); // New list of in-line layout if (!(m_currentParagraphInLines = psiconv_list_new(sizeof(struct psiconv_in_line_layout_s)))) goto ERROR3; m_inParagraph = true; // Apply all layout for this paragraph (besides the style) which applies to // the whole paragraph. if(!updateParagraphLayout(pAP,m_currentParagraphPLayout)) goto ERROR4; if(!updateCharacterLayout(pAP,m_currentParagraphCLayout)) goto ERROR4; return true; ERROR4: psiconv_list_free(m_currentParagraphInLines); m_currentParagraphInLines = NULL; ERROR3: psiconv_free_character_layout(m_currentParagraphCLayout); m_currentParagraphCLayout = NULL; ERROR2: psiconv_free_paragraph_layout(m_currentParagraphPLayout); m_currentParagraphPLayout = NULL; ERROR1: if (stylename_ucs2) free(stylename_ucs2); m_inParagraph = false; UT_DEBUGMSG(("PSION: open paragraph FAILED\n")); return false; } /*! Close the paragraph * * The current paragraph stuff is added to the paragraph list. * It is safe to call this function if no paragraph is opened. */ bool PL_Psion_Listener::_closeParagraph(void) { struct psiconv_paragraph_s para; // It is safe to call us when we are not actually in a paragraph; // we will just nop. if (m_inParagraph) { UT_DEBUGMSG(("PSION: Close paragraph\n")); // If we are not in a valid section, we will throw away the current // paragraph, with no real harm done. We need to deallocate a bunch // of things, though. if (m_sectionType == section_none) { UT_DEBUGMSG(("PSION: Closing paragraph while not in a section\n")); psiconv_list_empty(m_currentParagraphText); psiconv_free_character_layout(m_currentParagraphCLayout); m_currentParagraphCLayout = NULL; psiconv_free_paragraph_layout(m_currentParagraphPLayout); m_currentParagraphPLayout = NULL; psiconv_list_free(m_currentParagraphInLines); m_currentParagraphInLines = NULL; m_inParagraph = false; return true; } // Modify the list of Psiconv UCS2 characters to a Psiconv string // and put it in the paragraph. if (!(para.text = psiconv_unicode_from_list(m_currentParagraphText))) goto ERROR1; psiconv_list_empty(m_currentParagraphText); // Get the base paragraph and character layout, its style and inlines. para.base_character = m_currentParagraphCLayout; m_currentParagraphCLayout = NULL; para.base_paragraph = m_currentParagraphPLayout; m_currentParagraphPLayout = NULL; para.base_style = m_currentParagraphStyle; para.in_lines = m_currentParagraphInLines; m_currentParagraphInLines = NULL; // We don't do replacements, because I do not really understand // the Psion-side of it yet. if (!(para.replacements = psiconv_list_new(sizeof(struct psiconv_replacement_s)))) goto ERROR2; // Add the paragraph to the proper list. if (m_sectionType == section_main) { UT_DEBUGMSG(("PSION: Adding paragraph to the main section\n")); if (psiconv_list_add(m_paragraphs,¶)) goto ERROR3; } else if (m_sectionType == section_header) { UT_DEBUGMSG(("PSION: Adding paragraph to the header section\n")); if (psiconv_list_add(m_header->text->paragraphs,¶)) goto ERROR3; } else if (m_sectionType == section_footer) { UT_DEBUGMSG(("PSION: Adding paragraph to the footer section\n")); if (psiconv_list_add(m_footer->text->paragraphs,¶)) goto ERROR3; } m_inParagraph = false; } return true; // Cleanup if something goes wrong halfway-through. ERROR3: psiconv_list_free(para.replacements); ERROR2: psiconv_list_free(para.in_lines); psiconv_free_paragraph_layout(para.base_paragraph); psiconv_free_character_layout(para.base_character); free(para.text); ERROR1: m_inParagraph = false; UT_DEBUGMSG(("PSION: close paragraph FAILED\n")); return false; } /*! * Parse the current in-line formatting */ bool PL_Psion_Listener::_addInLine(const PT_AttrPropIndex api,UT_uint32 textlen) { const PP_AttrProp * pAP = NULL; psiconv_in_line_layout curInLine; UT_DEBUGMSG(("PSION: set inline formatting (%d)\n",textlen)); // We really should be in a paragraph right now. if (!m_inParagraph) { UT_ASSERT(m_inParagraph); goto ERROR1; } // Allocate a new psiconv_in_line_layout if (!(curInLine = (psiconv_in_line_layout) malloc(sizeof(*curInLine)))) goto ERROR1; // Fill CurInLine with sane defaults curInLine->object = NULL; curInLine->length = textlen; if (!(curInLine->layout = psiconv_clone_character_layout(m_currentParagraphCLayout))) goto ERROR2; // Set all character-based attributes if (m_pDocument->getAttrProp(api,&pAP) && pAP) { if (!updateCharacterLayout(pAP,curInLine->layout)) goto ERROR3; } // Add to the list of inlines if (psiconv_list_add(m_currentParagraphInLines,curInLine)) goto ERROR3; // Free the inline and return succes. free(curInLine); return true; ERROR3: psiconv_free_character_layout(curInLine->layout); ERROR2: free(curInLine); ERROR1: UT_DEBUGMSG(("PSION: set inline formatting FAILED\n")); return false; } /*! * Determine the layout of this style, considering the styles it is based on. * * A paragraph style is associated with a certain layout. That layout is * partly defined in the style itself, and partly because it inherits layout * from its parent style. This method gathers all that information, * translates it to Psiconv format and puts it in para_layout and * char_layout. * Note that we call ourselves recursively; if we ever encounter a * situation in which is style is based on itself, or is in some other circular * relationship, we will crash and burn. So don't do that :-) */ bool PL_Psion_Listener::_setStyleLayout(PD_Style *style, psiconv_paragraph_layout para_layout, psiconv_character_layout char_layout) { PT_AttrPropIndex api; const PP_AttrProp * pAP = NULL; PD_Style *parent_style = NULL; // If we are based on another style, we need to get its layout first, // so that we can overrule it. parent_style = style->getBasedOn(); if (parent_style) _setStyleLayout(parent_style,para_layout,char_layout); // Set the paragraph-level and character-level layouts. if ((api = style->getIndexAP()) && m_pDocument->getAttrProp(api,&pAP) && pAP) { if(!updateParagraphLayout(pAP,para_layout)) return false; if(!updateCharacterLayout(pAP,char_layout)) return false; } return true; } /*! * Translate all styles. * */ bool PL_Psion_Listener::_processStyles(void) { UT_GenericVector vecStyles; UT_sint32 i = 0; PD_Style * pStyle=NULL; psiconv_word_style style; // Allocate a temporary psiconv_word_style structure. if (!(style = (psiconv_word_style) malloc(sizeof(*style)))) goto ERROR1; // Initialize the styles section private variable. if (!(m_styles = (psiconv_word_styles_section) malloc(sizeof(*m_styles)))) goto ERROR1; if (!(m_styles->styles = psiconv_list_new(sizeof(*style)))) goto ERROR1; m_styles->normal = NULL; // Examine all used styles one by one (so we do not export unused styles. // You could discuss whether this is a good idea. I think so. I do not // want tens of unused styles in my Psion documents. Do you? m_pDocument->getAllUsedStyles(&vecStyles); for (i=0; i < vecStyles.getItemCount(); i++) { pStyle = vecStyles.getNthItem(i); // Psion does not understand character styles, so we ignore them. if (pStyle->isCharStyle()) continue; UT_DEBUGMSG(("PSION: Processing style %s\n",pStyle->getName())); // Initialize the style with sound defaults. if (!(style->character = psiconv_basic_character_layout())) goto ERROR2; if (!(style->paragraph = psiconv_basic_paragraph_layout())) goto ERROR3; style->hotkey = 0; style->built_in = psiconv_bool_false; style->outline_level = 0; // Set the style name if (!(style->name = utf8_to_ucs2(pStyle->getName()))) goto ERROR4; // Set the style layout _setStyleLayout(pStyle,style->paragraph,style->character); // Add the style to the styles section. // We handle the Normal style in a special way if (!strcmp(pStyle->getName(),"Normal")) { m_styles->normal = style; if (!(style = (psiconv_word_style) malloc(sizeof(*style)))) goto ERROR1; } else { if (psiconv_list_add(m_styles->styles,style)) goto ERROR5; } } // Just in case we somehow lost the Normal style (unlikely, but well...) if (!m_styles->normal) { // TODO: Perhaps we need better defaults here? UT_DEBUGMSG(("PSION: Creating our own Normal style %s\n",pStyle->getName())); if (!(style->character = psiconv_basic_character_layout())) goto ERROR2; if (!(style->paragraph = psiconv_basic_paragraph_layout())) goto ERROR3; style->hotkey = 0; style->built_in = psiconv_bool_false; style->outline_level = 0; if (!(style->name = utf8_to_ucs2("Normal"))) goto ERROR4; m_styles->normal = style; } else free(style); return true; ERROR5: free(style->name); ERROR4: psiconv_free_paragraph_layout(style->paragraph); ERROR3: psiconv_free_character_layout(style->character); ERROR2: free(style); ERROR1: return false; } /*! * Handle images * * Images can be in several flavours in the AbiWord source. Here, we just * handle PNG ones. */ bool PL_Psion_Listener::_insertImage(const PT_AttrPropIndex api) { const PP_AttrProp * pAP = NULL; const gchar *szValue; _bb image_data; std::string mimeType; const psiconv_ucs2 object_marker = 0x0e; png_structp png_ptr; png_infop info_ptr; int width,height,row,column,resx,resy; psiconv_paint_data_section paint_data; psiconv_sketch_section sketch_sec; psiconv_embedded_object_section object; psiconv_sketch_f sketch_file; struct psiconv_in_line_layout_s in_line; png_bytep *png_data_rows; // Get the attribute array if (!api || !m_pDocument->getAttrProp(api,&pAP) || !pAP) goto ERROR2; // Find the id of the image if (!pAP->getAttribute("dataid", szValue)) goto ERROR2; // Retrieve the image if (!m_pDocument->getDataItemDataByName(szValue,&(image_data.pBB), &mimeType,NULL)) goto ERROR2; image_data.iCurPos = 0; // At this moment, we only handle PNG images. Too bad. if (mimeType == "image/png") { UT_DEBUGMSG(("PSION: Unknown image MIME type (%s)\n", mimeType.c_str())); goto ERROR2; } // Prepare the PNG structure for reading png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) goto ERROR2; // Prepare the PNG structure for info info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr,NULL,NULL); goto ERROR2; } // Prepare PNG error handling if (setjmp(png_jmpbuf(png_ptr))) { UT_DEBUGMSG(("PSION: PNG Error handler\n")); goto ERROR3; } // Use our own functions for reading the PNG data stream png_set_read_fn(png_ptr,(void *) &image_data,read_png_data); // Read the image into memory into 8-bit RGB png_read_png(png_ptr,info_ptr,PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND,NULL); png_data_rows = png_get_rows(png_ptr,info_ptr); width = png_get_image_width(png_ptr,info_ptr); height = png_get_image_height(png_ptr,info_ptr); // If pixel ration unknown, use 72 DPI resx = png_get_x_pixels_per_meter(png_ptr,info_ptr); if (resx <= 0) resx = 72*40; resy = png_get_y_pixels_per_meter(png_ptr,info_ptr); if (resy <= 0) resy = 72*40; UT_DEBUGMSG(("PSION: Width %d, height %d, bitdepth %d, colortype %d, rowbytes: %lu\n", width,height,png_get_bit_depth(png_ptr,info_ptr), png_get_color_type(png_ptr,info_ptr), png_get_rowbytes(png_ptr,info_ptr))); // Allocate and fill all Psiconv structures. Quite a lot of work. if (!(paint_data = (psiconv_paint_data_section) malloc(sizeof(*paint_data)))) goto ERROR3; paint_data->xsize = width; paint_data->ysize = height; paint_data->pic_xsize = paint_data->pic_ysize = 0; if (!(paint_data->red = (float *) malloc(sizeof(float) * width * height))) goto ERROR4; if (!(paint_data->green = (float *) malloc(sizeof(float) * width * height))) goto ERROR5; if (!((paint_data->blue = (float *) malloc(sizeof(float) * width * height)))) goto ERROR6; for (row = 0; row < height; row++) { for (column = 0; column < width; column++) { paint_data->red[row*width+column] = png_data_rows[row][column*3] / 255.0; paint_data->green[row*width+column] = png_data_rows[row][column*3+1] / 255.0; paint_data->blue[row*width+column] = png_data_rows[row][column*3+2] / 255.0; UT_DEBUGMSG(("PSION: Pixeldata in: %02x %02x %02x, Pixels: %f %f %f\n", png_data_rows[row][column*3], png_data_rows[row][column*3+1], png_data_rows[row][column*3+2], paint_data->red[row*width+column], paint_data->green[row*width+column], paint_data->blue[row*width+column])); } } if (!(sketch_sec = (psiconv_sketch_section) malloc(sizeof(*sketch_sec)))) goto ERROR7; sketch_sec->displayed_xsize = sketch_sec->form_xsize = width; sketch_sec->displayed_ysize = sketch_sec->form_ysize = height; sketch_sec->picture_data_x_offset = sketch_sec->picture_data_y_offset = 0; sketch_sec->displayed_size_x_offset = sketch_sec->displayed_size_y_offset = 0; sketch_sec->magnification_x = sketch_sec->magnification_y = 1.0; sketch_sec->cut_left = sketch_sec->cut_right = sketch_sec->cut_top = sketch_sec->cut_bottom = 0.0; sketch_sec->picture = paint_data; if (!(sketch_file = (psiconv_sketch_f) malloc(sizeof(*sketch_file)))) goto ERROR8; sketch_file->sketch_sec = sketch_sec; if (!(object = (psiconv_embedded_object_section) malloc(sizeof(*object)))) goto ERROR9; if (!(object->icon = (psiconv_object_icon_section) malloc(sizeof(*object->icon)))) goto ERROR10; // Icon values are more or less random object->icon->icon_width = object->icon->icon_height = 0.5; if (!(object->icon->icon_name = utf8_to_ucs2("AbiWord Image"))) goto ERROR11; if (!(object->display = (psiconv_object_display_section) malloc(sizeof(*object->display)))) goto ERROR12; object->display->show_icon = psiconv_bool_false; object->display->width = width * 100 / resx; object->display->height = height * 100 / resy; if (!(object->object = (psiconv_file) malloc(sizeof(*object->object)))) goto ERROR13; object->object->type = psiconv_sketch_file; object->object->file = sketch_file; if (!(in_line.layout = psiconv_clone_character_layout(m_currentParagraphCLayout))) goto ERROR14; in_line.length = 1; in_line.object = object; in_line.object_width = width * 100 / resx; in_line.object_height = height * 100 / resy; if (psiconv_list_add(m_currentParagraphInLines,&in_line)) goto ERROR15; // Objects are represented by an object marker in the text. if (psiconv_list_add(m_currentParagraphText,&object_marker)) goto ERROR3; png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return true; ERROR15: psiconv_free_character_layout(in_line.layout); ERROR14: free(object->object); ERROR13: free(object->display); ERROR12: free(object->icon->icon_name); ERROR11: free(object->icon); ERROR10: free(object); ERROR9: free(sketch_file); ERROR8: free(sketch_sec); ERROR7: free(paint_data->blue); ERROR6: free(paint_data->green); ERROR5: free(paint_data->red); ERROR4: free(paint_data); ERROR3: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); ERROR2: UT_DEBUGMSG(("PSION: Parsing of image object failed\n")); return false; } /******************************* * class PL_Psion_Word_Listener * *******************************/ /*! * Create a Psion Word file */ psiconv_file PL_Psion_Word_Listener::createPsionFile(void) { psiconv_file psionfile; psiconv_word_f wordfile; // We base our file on an empty file. if (!(psionfile = psiconv_empty_file(psiconv_word_file))) return NULL; wordfile = (psiconv_word_f)(psionfile->file); // Free the old paragraphs structure and put ours into it. psiconv_free_text_and_layout(wordfile->paragraphs); wordfile->paragraphs = m_paragraphs; m_paragraphs = NULL; // Free the old styles list and put ours into it psiconv_free_word_styles_section(wordfile->styles_sec); wordfile->styles_sec = m_styles; m_styles = NULL; // Free the old header and footer and put ours into it psiconv_free_page_header(wordfile->page_sec->header); wordfile->page_sec->header = m_header; m_header = NULL; psiconv_free_page_header(wordfile->page_sec->footer); wordfile->page_sec->footer = m_footer; m_footer = NULL; // That's it! return psionfile; } /********************************* * class PL_Psion_TextEd_Listener * *********************************/ /*! * Create a Psion TextEd file */ psiconv_file PL_Psion_TextEd_Listener::createPsionFile(void) { psiconv_file psionfile; psiconv_texted_f textedfile; // We base our file on an empty file. if (!(psionfile = psiconv_empty_file(psiconv_texted_file))) return NULL; textedfile = (psiconv_texted_f)(psionfile->file); // Free the old paragraphs structure and put ours into it. psiconv_free_text_and_layout(textedfile->texted_sec->paragraphs); textedfile->texted_sec->paragraphs = m_paragraphs; m_paragraphs = NULL; // Free the old header and footer and put ours into it psiconv_free_page_header(textedfile->page_sec->header); textedfile->page_sec->header = m_header; psiconv_free_page_header(textedfile->page_sec->footer); textedfile->page_sec->footer = m_footer; // That's it! return psionfile; } /*************************** * class IE_Exp_Psion_Word * ***************************/ /*! * Create a Psion Word listener. */ PL_Psion_Listener *IE_Exp_Psion_Word::_constructListener(void) { return new PL_Psion_Word_Listener(getDoc()); } /***************************** * class IE_Exp_Psion_TextEd * *****************************/ /*! * Create a Psion TextEd listener. */ PL_Psion_Listener *IE_Exp_Psion_TextEd::_constructListener(void) { return new PL_Psion_TextEd_Listener(getDoc()); }