blob: 5358d6de15f12556a26c1b3b00f131db0ab98639 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "DFPlatform.h"
#include "WordStyles.h"
#include "DFDOM.h"
#include "DFHTML.h"
#include "DFXMLNames.h"
#include "CSS.h"
#include "CSSLength.h"
#include "CSSProperties.h"
#include "WordNumbering.h"
#include "WordTheme.h"
#include "WordSection.h"
#include "WordSheet.h"
#include "DFString.h"
#include "DFHashTable.h"
#include "DFCommon.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static Tag WordSectPr_Children[] = {
WORD_FOOTNOTEPR,
WORD_ENDNOTEPR,
WORD_TYPE,
WORD_PGSZ,
WORD_PGMAR,
WORD_PAPERSRC,
WORD_PGBORDERS,
WORD_LNNUMTYPE,
WORD_PGNUMTYPE,
WORD_COLS,
WORD_FORMPROT,
WORD_VALIGN,
WORD_NOENDNOTE,
WORD_TITLEPG,
WORD_TEXTDIRECTION,
WORD_BIDI,
WORD_RTLGUTTER,
WORD_DOCGRID,
WORD_PRINTERSETTINGS,
WORD_SECTPRCHANGE,
0,
};
static Tag WordStyle_Children[] = {
WORD_NAME,
WORD_ALIASES,
WORD_BASEDON,
WORD_NEXT,
WORD_LINK,
WORD_AUTOREDEFINE,
WORD_HIDDEN,
WORD_UIPRIORITY,
WORD_SEMIHIDDEN,
WORD_UNHIDEWHENUSED,
WORD_QFORMAT,
WORD_LOCKED,
WORD_PERSONAL,
WORD_PERSONALCOMPOSE,
WORD_PERSONALREPLY,
WORD_RSID,
WORD_PPR,
WORD_RPR,
WORD_TBLPR,
WORD_TRPR,
WORD_TCPR,
WORD_TBLSTYLEPR,
0,
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// Style definitions (elements in styles.xml) //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static void WordGetTblStylePr(DFNode *concrete, CSSStyle *style, WordSection *section, WordTheme *theme)
{
const char *tableComponent = DFGetAttribute(concrete,WORD_TYPE);
if (tableComponent == NULL)
return;;
CSSProperties *rule = CSSStyleRuleForTableComponent(style,tableComponent);
if (rule == NULL)
return;
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
const char *styleId = NULL;
switch (child->tag) {
case WORD_RPR:
WordGetRPr(child,rule,&styleId,theme);
break;
case WORD_PPR:
WordGetPPr(child,rule,&styleId,section);
break;
case WORD_TBLPR:
WordGetTblPr(child,rule,NULL,section,&styleId);
break;
case WORD_TRPR:
// FIXME
break;
case WORD_TCPR:
WordGetTcPr(child,rule);
break;
}
}
}
static void WordGetStyle(DFNode *concrete, CSSStyle *style, WordConverter *converter)
{
WordSection *section = converter->mainSection;
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
const char *styleId = NULL;
switch (child->tag) {
case WORD_NAME:
CSSStyleSetDisplayName(style,DFGetAttribute(child,WORD_VAL));
break;
case WORD_NEXT: {
// FIXME: do he need to handle style types other than paragraph here?
const char *nextName = DFGetAttribute(child,WORD_VAL);
if (nextName == NULL)
continue;
WordStyle *nextStyle = WordSheetStyleForTypeId(converter->styles,"paragraph",nextName);
if (nextStyle == NULL)
continue;
CSSStyleSetNext(style,nextStyle->selector);
break;
}
case WORD_RPR:
WordGetRPr(child,CSSStyleRule(style),&styleId,converter->theme);
break;
case WORD_PPR: {
WordGetPPr(child,CSSStyleRule(style),&styleId,section);
if (style->headingLevel > 0) {
DFNode *numPr = DFChildWithTag(child,WORD_NUMPR);
if (numPr != NULL)
WordGetNumPrStyle(numPr,style,converter);
}
break;
}
case WORD_TBLPR:
WordGetTblPr(child,CSSStyleRule(style),CSSStyleCell(style),section,&styleId);
break;
case WORD_TRPR:
// FIXME
break;
case WORD_TCPR:
WordGetTcPr(child,CSSStyleRule(style));
break;
case WORD_TBLSTYLEPR:
WordGetTblStylePr(child,style,section,converter->theme);
break;
}
}
// Special case: The ListParagraph style that word automatically adds specifies an indentation
// of 36pt. We don't actually want this, because HTML automatically indents lists anyway. If
// we see this, clear it, but keep the old value around for when we update the word document.
StyleFamily family = WordStyleFamilyForSelector(style->selector);
const char *name = WordSheetStyleIdForSelector(converter->styles,style->selector);
if ((family == StyleFamilyParagraph) && DFStringEquals(name,"ListParagraph")) {
CSSProperties *properties = CSSStyleRule(style);
const char *wordMarginLeft = CSSGet(properties,"margin-left");
CSSPut(properties,"-word-margin-left",wordMarginLeft);
CSSPut(properties,"margin-left",NULL);
}
DFNode *pPr = DFChildWithTag(concrete,WORD_PPR);
DFNode *numPr = DFChildWithTag(pPr,WORD_NUMPR);
const char *numId = DFGetChildAttribute(numPr,WORD_NUMID,WORD_VAL);
if ((numId != NULL) && (atoi(numId) == 0)) {
switch (style->tag) {
case HTML_H1:
case HTML_H2:
case HTML_H3:
case HTML_H4:
case HTML_H5:
case HTML_H6:
case HTML_FIGURE:
case HTML_TABLE: {
char *counterIncrement = DFFormatString("%s 0",style->elementName);
CSSPut(CSSStyleRule(style),"counter-reset","null");
CSSPut(CSSStyleRule(style),"counter-increment",counterIncrement);
CSSPut(CSSStyleBefore(style),"content","none");
free(counterIncrement);
}
}
}
}
static void WordPutStyle(DFNode *concrete, CSSStyle *style, WordConverter *converter)
{
// If we find a style with a counter-increment value of "<elementname> 0", this means that it's
// an explicitly unnumbered paragraph. So we set the numbering id to 0, so word doesn't increment
// the corresponding counter when encountering this style
if (CSSGet(CSSStyleRule(style),"counter-increment") != NULL) {
char *zeroIncrement = DFFormatString("%s 0",style->elementName);
if (DFStringEquals(CSSGet(CSSStyleRule(style),"counter-increment"),zeroIncrement))
CSSPut(CSSStyleRule(style),"-word-numId","0");
free(zeroIncrement);
}
int outlineLvl = -1;
if ((style->headingLevel >= 1) && (style->headingLevel <= 6)) {
outlineLvl = style->headingLevel-1;
char *nextSelector = CSSStyleCopyNext(style);
if (nextSelector == NULL) {
CSSStyle *def = CSSSheetDefaultStyleForFamily(converter->styleSheet,StyleFamilyParagraph);
if (def != NULL)
CSSStyleSetNext(style,def->selector);
}
free(nextSelector);
}
DFNode *children[PREDEFINED_TAG_COUNT];
childrenToArray(concrete,children);
char *parentSelector = CSSStyleCopyParent(style);
if (parentSelector == NULL) {
if (style->className != NULL) {
if ((style->tag != HTML_P) && (style->tag != HTML_SPAN) && (style->tag != HTML_TABLE)) {
CSSStyle *parentStyle = CSSSheetLookupElement(converter->styleSheet,style->elementName,NULL,0,0);
if ((parentStyle != NULL) && !parentStyle->latent)
parentSelector = xstrdup(style->elementName);
}
}
}
// Based on
const char *basedOn = WordSheetStyleIdForSelector(converter->styles,parentSelector);
if (basedOn == NULL) {
children[WORD_BASEDON] = NULL;
}
else {
children[WORD_BASEDON] = DFCreateElement(concrete->doc,WORD_BASEDON);
DFSetAttribute(children[WORD_BASEDON],WORD_VAL,basedOn);
}
// Style for next paragraph
children[WORD_NEXT] = NULL;
char *nextSelector = CSSStyleCopyNext(style);
if (nextSelector != NULL) {
StyleFamily thisFamily = WordStyleFamilyForSelector(style->selector);
StyleFamily nextFamily = WordStyleFamilyForSelector(nextSelector);
if (nextFamily == thisFamily) {
children[WORD_NEXT] = DFCreateElement(concrete->doc,WORD_NEXT);
const char *nextStyleId = WordSheetStyleIdForSelector(converter->styles,nextSelector);
DFSetAttribute(children[WORD_NEXT],WORD_VAL,nextStyleId);
}
}
if (WordStyleFamilyForSelector(style->selector) == StyleFamilyTable) {
// Table properties
if (children[WORD_TBLPR] == NULL)
children[WORD_TBLPR] = DFCreateElement(concrete->doc,WORD_TBLPR);;
const char *oldJc = DFGetChildAttribute(children[WORD_TBLPR],WORD_JC,WORD_VAL);
CSSProperties *wholeTable = CSSStyleRuleForSuffix(style,DFTableSuffixWholeTable);
WordPutTblPr(children[WORD_TBLPR],wholeTable,CSSStyleCell(style),converter->mainSection,NULL);
const char *newJc = DFGetChildAttribute(children[WORD_TBLPR],WORD_JC,WORD_VAL);
if (children[WORD_TBLPR]->first == NULL)
children[WORD_TBLPR] = NULL;
if (children[WORD_TRPR] == NULL)
children[WORD_TRPR] = DFCreateElement(concrete->doc,WORD_TRPR);
WordPutTrPr(children[WORD_TRPR],oldJc,newJc);
if (children[WORD_TRPR]->first == NULL)
children[WORD_TRPR] = NULL;
}
else {
// Paragraph properties
if (children[WORD_PPR] == NULL)
children[WORD_PPR] = DFCreateElement(concrete->doc,WORD_PPR);
WordPutPPr(children[WORD_PPR],CSSStyleRule(style),NULL,converter->mainSection,outlineLvl);
if (children[WORD_PPR]->first == NULL)
children[WORD_PPR] = NULL;
// Run properties
if (children[WORD_RPR] == NULL)
children[WORD_RPR] = DFCreateElement(concrete->doc,WORD_RPR);
WordPutRPr(children[WORD_RPR],CSSStyleRule(style),NULL,converter->theme);
if (children[WORD_RPR]->first == NULL)
children[WORD_RPR] = NULL;
}
replaceChildrenFromArray(concrete,children,WordStyle_Children);
free(parentSelector);
free(nextSelector);
}
static void WordGetSectPr(DFNode *concrete, CSSProperties *body, CSSProperties *page, WordSection *section)
{
DFNode *pgSz = NULL;
DFNode *pgMar = NULL;
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
switch (child->tag) {
case WORD_PGSZ:
pgSz = child;
break;
case WORD_PGMAR:
pgMar = child;
break;
}
}
if ((pgSz != NULL) && (pgMar != NULL)) {
const char *widthStr = DFGetAttribute(pgSz,WORD_W);
const char *heightStr = DFGetAttribute(pgSz,WORD_H);
if (widthStr != NULL)
section->pageWidth = atoi(widthStr);
if (heightStr != NULL)
section->pageHeight = atoi(heightStr);
if ((widthStr != NULL) && (heightStr != NULL)) {
const char *leftStr = DFGetAttribute(pgMar,WORD_LEFT);
const char *rightStr = DFGetAttribute(pgMar,WORD_RIGHT);
const char *topStr = DFGetAttribute(pgMar,WORD_TOP);
const char *bottomStr = DFGetAttribute(pgMar,WORD_BOTTOM);
// In CSS, margins (both horizontal and vertical) are measured relative to the width of
// the containing block, when expressed as percentages
double width = atof(widthStr);
if (leftStr != NULL) {
double leftPct = 100.0*atof(leftStr)/width;
char buf[100];
CSSPut(body,"margin-left",DFFormatDoublePct(buf,100,leftPct));
section->leftMargin = atoi(leftStr);
}
if (rightStr != NULL) {
double rightPct = 100.0*atof(rightStr)/width;
char buf[100];
CSSPut(body,"margin-right",DFFormatDoublePct(buf,100,rightPct));
section->rightMargin = atoi(rightStr);
}
if (topStr != NULL) {
double topPct = 100.0*atof(topStr)/width;
char buf[100];
CSSPut(body,"margin-top",DFFormatDoublePct(buf,100,topPct));
section->topMargin = atoi(topStr);
}
if (bottomStr != NULL) {
double bottomPct = 100.0*atof(bottomStr)/width;
char buf[100];
CSSPut(body,"margin-bottom",DFFormatDoublePct(buf,100,bottomPct));
section->bottomMargin = atoi(bottomStr);
}
// A "twip" is a twentieth of a point
int widthTwips = atoi(widthStr);
int heightTwips = atoi(heightStr);
if ((widthTwips == A4_WIDTH_TWIPS) && (heightTwips == A4_HEIGHT_TWIPS))
CSSPut(page,"size","A4 portrait");
else if ((widthTwips == A4_HEIGHT_TWIPS) && (heightTwips == A4_WIDTH_TWIPS))
CSSPut(page,"size","A4 landscape");
else if ((widthTwips == LETTER_WIDTH_TWIPS) && (heightTwips == LETTER_HEIGHT_TWIPS))
CSSPut(page,"size","letter portrait");
else if ((widthTwips == LETTER_HEIGHT_TWIPS) && (heightTwips == LETTER_WIDTH_TWIPS))
CSSPut(page,"size","letter landscape");
}
}
}
static void WordPutSectPr(DFNode *concrete, CSSSheet *styleSheet, WordSection *section)
{
// Note: The sectPr element can potentially contain one or more headerReference or
// footerReference elements at the start, before the elements in WordSectPr_Children.
// So the straight childrenToArray/replaceChildrenFromArray method used elsewhere doesn't
// work here - we need to make sure these other elements are maintained
DFNode *children[PREDEFINED_TAG_COUNT];
childrenToArray(concrete,children);
CSSProperties *oldBody = CSSPropertiesNew();
CSSProperties *oldPage = CSSPropertiesNew();
WordGetSectPr(concrete,oldBody,oldPage,section);
CSSProperties *newBody = CSSSheetBodyProperties(styleSheet);
CSSProperties *newPage = CSSSheetPageProperties(styleSheet);
// Page size
if (children[WORD_PGSZ] == NULL)
children[WORD_PGSZ] = DFCreateElement(concrete->doc,WORD_PGSZ);
int updatePageSize = 0;
const char *widthStr = DFGetAttribute(children[WORD_PGSZ],WORD_W);
const char *heightStr = DFGetAttribute(children[WORD_PGSZ],WORD_H);
int widthTwips;
int heightTwips;
if ((widthStr == NULL) || (atoi(widthStr) <= 0) ||
(heightStr == NULL) || (atoi(heightStr) <= 0)) {
// Invalid or missing page size: Set to A4 portrait
widthTwips = A4_WIDTH_TWIPS;
heightTwips = A4_HEIGHT_TWIPS;
updatePageSize = 1;
}
else {
widthTwips = atoi(widthStr);
heightTwips = atoi(heightStr);
}
if (!DFStringEquals(CSSGet(oldPage,"size"),CSSGet(newPage,"size"))) {
const char *newSize = CSSGet(newPage,"size");
if (DFStringEqualsCI(newSize,"A4 portrait")) {
widthTwips = A4_WIDTH_TWIPS;
heightTwips = A4_HEIGHT_TWIPS;
}
else if (DFStringEqualsCI(newSize,"A4 landscape")) {
widthTwips = A4_HEIGHT_TWIPS;
heightTwips = A4_WIDTH_TWIPS;
}
else if (DFStringEqualsCI(newSize,"letter portrait")) {
widthTwips = LETTER_WIDTH_TWIPS;
heightTwips = LETTER_HEIGHT_TWIPS;
}
else if (DFStringEqualsCI(newSize,"letter landscape")) {
widthTwips = LETTER_HEIGHT_TWIPS;
heightTwips = LETTER_WIDTH_TWIPS;
}
else {
widthTwips = A4_WIDTH_TWIPS;
heightTwips = A4_HEIGHT_TWIPS;
}
updatePageSize = 1;
}
if (updatePageSize) {
DFFormatAttribute(children[WORD_PGSZ],WORD_W,"%d",widthTwips);
DFFormatAttribute(children[WORD_PGSZ],WORD_H,"%d",heightTwips);
if (widthTwips > heightTwips)
DFSetAttribute(children[WORD_PGSZ],WORD_ORIENT,"landscape");
else
DFRemoveAttribute(children[WORD_PGSZ],WORD_ORIENT);
}
if (children[WORD_PGMAR] == NULL)
children[WORD_PGMAR] = DFCreateElement(concrete->doc,WORD_PGMAR);
// Page margins
if (!DFStringEquals(CSSGet(oldBody,"margin-left"),CSSGet(newBody,"margin-left")) || updatePageSize)
updateTwipsFromLength(children[WORD_PGMAR],WORD_LEFT,CSSGet(newBody,"margin-left"),widthTwips);
if (!DFStringEquals(CSSGet(oldBody,"margin-right"),CSSGet(newBody,"margin-right")) || updatePageSize)
updateTwipsFromLength(children[WORD_PGMAR],WORD_RIGHT,CSSGet(newBody,"margin-right"),widthTwips);
if (!DFStringEquals(CSSGet(oldBody,"margin-top"),CSSGet(newBody,"margin-top")) || updatePageSize)
updateTwipsFromLength(children[WORD_PGMAR],WORD_TOP,CSSGet(newBody,"margin-top"),widthTwips);
if (!DFStringEquals(CSSGet(oldBody,"margin-bottom"),CSSGet(newBody,"margin-bottom")) || updatePageSize)
updateTwipsFromLength(children[WORD_PGMAR],WORD_BOTTOM,CSSGet(newBody,"margin-bottom"),widthTwips);
if (children[WORD_PGMAR]->attrsCount == 0)
children[WORD_PGMAR] = NULL;;
DFArray *extra = DFArrayNew(NULL,NULL);
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
switch (child->tag) {
case WORD_HEADERREFERENCE:
case WORD_FOOTERREFERENCE:
DFArrayAppend(extra,child);
break;
}
}
replaceChildrenFromArray(concrete,children,WordSectPr_Children);
for (long i = (long)(DFArrayCount(extra)-1); i >= 0; i--) {
DFNode *child = DFArrayItemAt(extra,i);
DFInsertBefore(concrete,child,concrete->first);
}
DFArrayRelease(extra);
CSSPropertiesRelease(oldBody);
CSSPropertiesRelease(oldPage);
}
static DFNode *findSectPr(WordConverter *converter, int add)
{
DFNode *root = converter->package->document->root;
if (root->tag != WORD_DOCUMENT)
return NULL;;
DFNode *body = DFChildWithTag(root,WORD_BODY);
if (body == NULL)
return NULL;;
DFNode *sectPr = DFChildWithTag(body,WORD_SECTPR);
if ((sectPr == NULL) & add) {
sectPr = DFCreateElement(converter->package->document,WORD_SECTPR);
DFAppendChild(body,sectPr);
}
return sectPr;
}
static void parseBody(WordConverter *converter, CSSSheet *styleSheet)
{
DFNode *sectPr = findSectPr(converter,0);
if (sectPr == NULL)
return;
WordGetSectPr(sectPr,CSSSheetBodyProperties(styleSheet),CSSSheetPageProperties(styleSheet),converter->mainSection);
}
static void updateBody(WordConverter *converter, CSSSheet *styleSheet)
{
DFNode *sectPr = findSectPr(converter,1);
if (sectPr == NULL)
return;
WordPutSectPr(sectPr,styleSheet,converter->mainSection);
}
static void styleParse(WordStyle *wordStyle, WordConverter *converter, CSSStyle *style)
{
DFNode *concrete = wordStyle->element;
WordGetStyle(concrete,style,converter);
if (wordStyle->basedOn != NULL) {
WordStyle *parent = WordSheetStyleForTypeId(converter->styles,wordStyle->type,wordStyle->basedOn);
if (parent != NULL) {
CSSStyleSetParent(style,parent->selector);
}
}
}
static void fixParagraphSpacing(CSSStyle *style, DFNode *element)
{
DFNode *pPr = DFChildWithTag(element,WORD_PPR);
DFNode *spacing = DFChildWithTag(pPr,WORD_SPACING);
const char *beforeAuto = DFGetAttribute(spacing,WORD_BEFOREAUTOSPACING);
const char *afterAuto = DFGetAttribute(spacing,WORD_AFTERAUTOSPACING);
CSSProperties *rule = CSSStyleRule(style);
if (beforeAuto != NULL) {
if (Word_parseOnOff(beforeAuto))
CSSPut(rule,"margin-top",NULL);
else
CSSPut(rule,"margin-top","0");
}
else {
if (CSSGet(rule,"margin-top") == NULL)
CSSPut(rule,"margin-top","0");
}
int isBeforeAuto = ((beforeAuto != NULL) && Word_parseOnOff(beforeAuto));
int isAfterAuto = ((afterAuto != NULL) && Word_parseOnOff(afterAuto));
if (isBeforeAuto)
CSSPut(rule,"margin-top",NULL);
else if (CSSGet(rule,"margin-top") == NULL)
CSSPut(rule,"margin-top","0");
if (isAfterAuto)
CSSPut(rule,"margin-bottom",NULL);
else if (CSSGet(rule,"margin-bottom") == NULL)
CSSPut(rule,"margin-bottom","0");
}
CSSSheet *WordParseStyles(WordConverter *converter)
{
CSSSheet *styleSheet = CSSSheetNew();
CSSStyle *bodyStyle = CSSSheetLookupElement(styleSheet,"body",NULL,1,0);
CSSPut(CSSStyleRule(bodyStyle),"counter-reset","h1 h2 h3 h4 h5 h6 figure table");
parseBody(converter,styleSheet);
if (converter->package->styles == NULL)
return styleSheet;;
DFNode *root = converter->package->styles->root;
if (root == NULL)
return styleSheet;
if (root->tag != WORD_STYLES)
return styleSheet;;
WordSheet *sheet = converter->styles;
const char **allIdents = WordSheetCopyIdents(sheet);
for (int i = 0; allIdents[i]; i++) {
WordStyle *wordStyle = WordSheetStyleForIdent(sheet,allIdents[i]);
if ((wordStyle->selector == NULL) || WordStyleIsProtected(wordStyle))
continue;
CSSStyle *style = CSSSheetLookupSelector(styleSheet,wordStyle->selector,1,0);
styleParse(wordStyle,converter,style);
const char *defaultVal = DFGetAttribute(wordStyle->element,WORD_DEFAULT);
if ((defaultVal != NULL) && Word_parseOnOff(defaultVal)) {
StyleFamily family = WordStyleFamilyForSelector(style->selector);
CSSSheetSetDefaultStyle(styleSheet,style,family);
CSSSetDefault(CSSStyleRule(style),1);
if (family == StyleFamilyParagraph)
fixParagraphSpacing(style,wordStyle->element);
}
}
free(allIdents);
DFNode *docDefaults = DFChildWithTag(root,WORD_DOCDEFAULTS);
DFNode *pPrDefault = DFChildWithTag(docDefaults,WORD_PPRDEFAULT);
DFNode *pPr = DFChildWithTag(pPrDefault,WORD_PPR);
if (pPr != NULL) {
CSSStyle *body = CSSSheetLookupElement(styleSheet,"body",NULL,1,0);
const char *styleId = NULL;
WordGetPPr(pPr,CSSStyleRule(body),&styleId,converter->mainSection);
}
// Special case for figure style: set left and right margin to auto, if not already set
CSSStyle *figure = CSSSheetLookupElement(styleSheet,"figure",NULL,0,0);
if (figure != NULL) {
if (CSSGet(CSSStyleRule(figure),"margin-left") == NULL)
CSSPut(CSSStyleRule(figure),"margin-left","auto");
if (CSSGet(CSSStyleRule(figure),"margin-right") == NULL)
CSSPut(CSSStyleRule(figure),"margin-right","auto");
}
return styleSheet;
}
static void updateDefault(CSSStyle *style, DFNode *element, CSSSheet *styleSheet, WordConverter *converter)
{
StyleFamily family = WordStyleFamilyForSelector(style->selector);
if (CSSSheetDefaultStyleForFamily(styleSheet,family) == style)
DFSetAttribute(element,WORD_DEFAULT,"1");
else
DFRemoveAttribute(element,WORD_DEFAULT);
}
static void updateDefaults(WordConverter *converter, CSSSheet *styleSheet)
{
DFNode *root = converter->package->styles->root;
CSSStyle *bodyStyle = CSSSheetLookupElement(converter->styleSheet,"body",NULL,0,0);
if (bodyStyle != NULL) {
// Remove margin properties
DFHashTable *collapsed = CSSCollapseProperties(CSSStyleRule(bodyStyle));
CSSProperties *copy = CSSPropertiesNewWithRaw(collapsed);
DFHashTableRelease(collapsed);
CSSPut(copy,"margin-top",NULL);
CSSPut(copy,"margin-bottom",NULL);
CSSPut(copy,"margin-left",NULL);
CSSPut(copy,"margin-right",NULL);
DFNode *docDefaults = DFChildWithTag(root,WORD_DOCDEFAULTS);
DFNode *rPrDefault = DFChildWithTag(docDefaults,WORD_RPRDEFAULT);
DFNode *pPrDefault = DFChildWithTag(docDefaults,WORD_PPRDEFAULT);
DFNode *rPr = DFChildWithTag(rPrDefault,WORD_RPR);
DFNode *pPr = DFChildWithTag(pPrDefault,WORD_PPR);
int hadEmptyRPrDefault = ((rPrDefault != NULL) && (rPrDefault->first == NULL));
int hadEmptyPPrDefault = ((pPrDefault != NULL) && (pPrDefault->first == NULL));
if (docDefaults == NULL)
docDefaults = DFCreateElement(converter->package->styles,WORD_DOCDEFAULTS);
if (rPrDefault == NULL)
rPrDefault = DFCreateElement(converter->package->styles,WORD_RPRDEFAULT);
if (pPrDefault == NULL)
pPrDefault = DFCreateElement(converter->package->styles,WORD_PPRDEFAULT);
if (rPr == NULL)
rPr = DFCreateChildElement(rPrDefault,WORD_RPR);
if (pPr == NULL)
pPr = DFCreateChildElement(pPrDefault,WORD_PPR);
DFAppendChild(docDefaults,rPrDefault);
DFAppendChild(docDefaults,pPrDefault);
DFInsertBefore(root,docDefaults,root->first);
WordPutPPr(pPr,copy,NULL,converter->mainSection,-1);
if (rPr->first == NULL)
DFRemoveNode(rPr);
if (pPr->first == NULL)
DFRemoveNode(pPr);
if ((rPrDefault->first == NULL) && !hadEmptyRPrDefault)
DFRemoveNode(rPrDefault);
if ((pPrDefault->first == NULL) && !hadEmptyPPrDefault)
DFRemoveNode(pPrDefault);
if (docDefaults->first == NULL)
DFRemoveNode(docDefaults);
CSSPropertiesRelease(copy);
}
}
static DFHashTable *WordSheetFindUsedConcreteNumIds(WordSheet *sheet)
{
DFHashTable *concreteNumIds = DFHashTableNew(NULL,NULL); // Used as a set
const char **allIdents = WordSheetCopyIdents(sheet);
for (int i = 0; allIdents[i]; i++) {
const char *ident = allIdents[i];
WordStyle *style = WordSheetStyleForIdent(sheet,ident);
DFNode *pPr = DFChildWithTag(style->element,WORD_PPR);
DFNode *numPr = DFChildWithTag(pPr,WORD_NUMPR);
const char *numId = DFGetChildAttribute(numPr,WORD_NUMID,WORD_VAL);
if (numId != NULL)
DFHashTableAdd(concreteNumIds,numId,"");
}
free(allIdents);
return concreteNumIds;
}
static char *WordStyleNameForStyle(CSSStyle *style)
{
char *displayName = CSSStyleCopyDisplayName(style);
if (displayName != NULL)
return displayName;
if (style->className != NULL)
return WordStyleNameFromClassName(style->className);
switch (style->tag) {
case HTML_H1: return xstrdup("heading 1");
case HTML_H2: return xstrdup("heading 2");
case HTML_H3: return xstrdup("heading 3");
case HTML_H4: return xstrdup("heading 4");
case HTML_H5: return xstrdup("heading 5");
case HTML_H6: return xstrdup("heading 6");
case HTML_FIGURE: return xstrdup("Figure");
}
return NULL;
}
void WordUpdateStyles(WordConverter *converter, CSSSheet *styleSheet)
{
CSSStyle *paraDefault = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyParagraph);
if (CSSGet(CSSStyleRule(paraDefault),"margin-top") == NULL)
CSSPut(CSSStyleRule(paraDefault),"margin-top","-word-auto");
if (CSSGet(CSSStyleRule(paraDefault),"margin-bottom") == NULL)
CSSPut(CSSStyleRule(paraDefault),"margin-bottom","-word-auto");
if (converter->package->styles == NULL) // FIXME: create this document
return;;
DFNode *root = converter->package->styles->root;
if ((root == NULL) || (root->tag != WORD_STYLES))
return;;
DFHashTable *remainingSelectors = DFHashTableNew(NULL,NULL); // Used as a set
const char **allSelectors = CSSSheetCopySelectors(styleSheet);
for (int i = 0; allSelectors[i]; i++) {
const char *selector = allSelectors[i];
DFHashTableAdd(remainingSelectors,selector,"");
}
free(allSelectors);
WordSheet *sheet = converter->styles;
DFHashTable *oldConcreteNumIds = WordSheetFindUsedConcreteNumIds(sheet);
updateNumbering(converter,styleSheet);
// Update or remove existing styles
const char **allIdents = WordSheetCopyIdents(sheet);
for (int i = 0; allIdents[i]; i++) {
WordStyle *wordStyle = WordSheetStyleForIdent(sheet,allIdents[i]);
DFNode *element = wordStyle->element;
if (WordStyleIsProtected(wordStyle)) {
DFHashTableRemove(remainingSelectors,wordStyle->selector);
continue;
}
if (!DFStringEquals(wordStyle->type,"paragraph") &&
!DFStringEquals(wordStyle->type,"character") &&
!DFStringEquals(wordStyle->type,"table"))
continue;
CSSStyle *cssStyle = CSSSheetLookupSelector(styleSheet,wordStyle->selector,0,0);
if (cssStyle == NULL) {
// Remove style
WordSheetRemoveStyle(sheet,wordStyle);
continue;
}
// Update style
WordPutStyle(element,cssStyle,converter);
updateDefault(cssStyle,element,styleSheet,converter);
DFHashTableRemove(remainingSelectors,wordStyle->selector);
}
free(allIdents);
// Sort the list of new styles, so that test output is deterministic
const char **sortedSelectors = DFHashTableCopyKeys(remainingSelectors);
DFSortStringsCaseInsensitive(sortedSelectors);
// Add new styles. We do this in two stages - first creating the styles, and then setting their properties.
// This is because the second stage depends on referenced styles (e.g. based on and next) to be already
// present.
for (int selIndex = 0; sortedSelectors[selIndex]; selIndex++) {
const char *selector = sortedSelectors[selIndex];
CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0);
const char *familyStr = NULL;
StyleFamily family = WordStyleFamilyForSelector(selector);
if (family == StyleFamilyParagraph)
familyStr = "paragraph";
else if (family == StyleFamilyCharacter)
familyStr = "character";
else if (family == StyleFamilyTable)
familyStr = "table";
else
continue;
char *styleId = WordStyleIdForStyle(style);
char *name = WordStyleNameForStyle(style);
if (name == NULL)
name = xstrdup(styleId);;
WordStyle *wordStyle = WordSheetAddStyle(sheet,familyStr,styleId,name,selector);
DFCreateChildElement(wordStyle->element,WORD_QFORMAT);
free(styleId);
free(name);
}
for (int selIndex = 0; sortedSelectors[selIndex]; selIndex++) {
const char *selector = sortedSelectors[selIndex];
StyleFamily family = WordStyleFamilyForSelector(selector);
if ((family != StyleFamilyParagraph) &&
(family != StyleFamilyCharacter) &&
(family != StyleFamilyTable))
continue;
CSSStyle *style = CSSSheetLookupSelector(styleSheet,selector,0,0);
WordStyle *wordStyle = WordSheetStyleForSelector(converter->styles,selector);
assert(wordStyle != NULL);
CSSStyleAddDefaultHTMLProperties(style);
// FIXME: language
// FIXME: not covered by tests
if ((style->headingLevel >= 1) && (style->headingLevel <= 6))
CSSStyleSetNext(style,"p.Normal");
WordPutStyle(wordStyle->element,style,converter);
updateDefault(style,wordStyle->element,styleSheet,converter);
}
free(sortedSelectors);
// Update body style (document defaults)
updateDefaults(converter,styleSheet);
updateBody(converter,styleSheet);
DFHashTable *newConcreteNumIds = WordSheetFindUsedConcreteNumIds(sheet);
const char **oldKeys = DFHashTableCopyKeys(oldConcreteNumIds);
for (int oldIndex = 0; oldKeys[oldIndex]; oldIndex++) {
const char *numId = oldKeys[oldIndex];
if (DFHashTableLookup(newConcreteNumIds,numId) == NULL) {
WordConcreteNum *concreteNum = WordNumberingConcreteWithId(converter->numbering,numId);
if (concreteNum != NULL)
WordNumberingRemoveConcrete(converter->numbering,concreteNum);
}
}
free(oldKeys);
DFHashTableRelease(remainingSelectors);
DFHashTableRelease(oldConcreteNumIds);
DFHashTableRelease(newConcreteNumIds);
}
CSSStyle *WordSetupTableGridStyle(CSSSheet *styleSheet, int *changed)
{
CSSStyle *style = CSSSheetLookupElement(styleSheet,"table","Table_Grid",0,0);
if (style == NULL) {
style = CSSSheetLookupElement(styleSheet,"table","Table_Grid",1,0);
CSSProperties *border = CSSPropertiesNewWithString("border: 1px solid black");
DFHashTable *collapsed = CSSCollapseProperties(border);
CSSPropertiesUpdateFromRaw(CSSStyleRule(style),collapsed);
CSSPropertiesUpdateFromRaw(CSSStyleCell(style),collapsed);
DFHashTableRelease(collapsed);
CSSPut(CSSStyleRule(style),"margin-left","auto");
CSSPut(CSSStyleRule(style),"margin-right","auto");
// These must be set last, as updateRaw clears them when modifying rule
CSSStyleSetParent(style,"table.Normal_Table");
CSSStyleSetDisplayName(style,"Table Grid");
if (changed != NULL)
*changed = 1;
CSSPropertiesRelease(border);
}
return style;
}