blob: 022ba5e1f0c79f4f1f2ba5bec3c4c44840436a9a [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 "WordPPr.h"
#include "WordStyles.h"
#include "WordSection.h"
#include "CSS.h"
#include "DFHTML.h"
#include "DFString.h"
#include "DFCommon.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
static Tag WordNumPr_Children[] = {
WORD_ILVL,
WORD_NUMID,
WORD_NUMBERINGCHANGE,
WORD_INS,
0,
};
static Tag WordPPR_Children[] = {
WORD_PSTYLE,
WORD_KEEPNEXT,
WORD_KEEPLINES,
WORD_PAGEBREAKBEFORE,
WORD_FRAMEPR,
WORD_WIDOWCONTROL,
WORD_NUMPR,
WORD_SUPPRESSLINENUMBERS,
WORD_PBDR,
WORD_SHD,
WORD_TABS,
WORD_SUPPRESSAUTOHYPHENS,
WORD_KINSOKU,
WORD_WORDWRAP,
WORD_OVERFLOWPUNCT,
WORD_TOPLINEPUNCT,
WORD_AUTOSPACEDE,
WORD_AUTOSPACEDN,
WORD_BIDI,
WORD_ADJUSTRIGHTIND,
WORD_SNAPTOGRID,
WORD_SPACING,
WORD_IND,
WORD_CONTEXTUALSPACING,
WORD_MIRRORINDENTS,
WORD_SUPPRESSOVERLAP,
WORD_JC,
WORD_TEXTDIRECTION,
WORD_TEXTALIGNMENT,
WORD_TEXTBOXTIGHTWRAP,
WORD_OUTLINELVL,
WORD_DIVID,
WORD_CNFSTYLE,
WORD_RPR,
WORD_SECTPR,
WORD_PPRCHANGE,
0,
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// Paragraph properties (pPr element) //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static void WordGetPBdr(DFNode *concrete, CSSProperties *properties)
{
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
switch (child->tag) {
case WORD_TOP:
WordGetBorder(child,"top",properties);
break;
case WORD_BOTTOM:
WordGetBorder(child,"bottom",properties);
break;
case WORD_LEFT:
WordGetBorder(child,"left",properties);
break;
case WORD_RIGHT:
WordGetBorder(child,"right",properties);
break;
default:
break;
}
}
}
static void WordPutPBdr(DFNode *concrete, CSSProperties *oldp, CSSProperties *newp)
{
DFNode *children[PREDEFINED_TAG_COUNT];
bzero(children,PREDEFINED_TAG_COUNT*sizeof(DFNode *));
childrenToArray(concrete,children);
DFDocument *doc = concrete->doc;
WordPutBorder(doc,oldp,newp,&children[WORD_TOP],WORD_TOP,"top");
WordPutBorder(doc,oldp,newp,&children[WORD_BOTTOM],WORD_BOTTOM,"bottom");
WordPutBorder(doc,oldp,newp,&children[WORD_LEFT],WORD_LEFT,"left");
WordPutBorder(doc,oldp,newp,&children[WORD_RIGHT],WORD_RIGHT,"right");
Tag WordPBdr_children[] = {
WORD_TOP,
WORD_LEFT,
WORD_BOTTOM,
WORD_RIGHT,
WORD_BETWEEN,
WORD_BAR,
0,
};
replaceChildrenFromArray(concrete,children,WordPBdr_children);
}
static void WordGetNumPr(DFNode *concrete, CSSProperties *properties)
{
for (DFNode *child = concrete->first; child != NULL; child = child->next) {
switch (child->tag) {
case WORD_NUMID:
CSSPut(properties,"-word-numId",DFGetAttribute(child,WORD_VAL));
break;
case WORD_ILVL:
CSSPut(properties,"-word-ilvl",DFGetAttribute(child,WORD_VAL));
break;
}
}
}
static void WordPutNumPr(DFNode *concrete, CSSProperties *newp)
{
DFNode *children[PREDEFINED_TAG_COUNT];
childrenToArray(concrete,children);
CSSProperties *oldp = CSSPropertiesNew();
WordGetNumPr(concrete,oldp);
if (!DFStringEquals(CSSGet(oldp,"-word-numId"),CSSGet(newp,"-word-numId")) ||
!DFStringEquals(CSSGet(oldp,"-word-ilvl"),CSSGet(newp,"-word-ilvl"))) {
if (CSSGet(newp,"-word-numId") != NULL) {
children[WORD_NUMID] = DFCreateElement(concrete->doc,WORD_NUMID);
DFSetAttribute(children[WORD_NUMID],WORD_VAL,CSSGet(newp,"-word-numId"));
if (CSSGet(newp,"-word-ilvl") != NULL) {
children[WORD_ILVL] = DFCreateElement(concrete->doc,WORD_ILVL);
DFSetAttribute(children[WORD_ILVL],WORD_VAL,CSSGet(newp,"-word-ilvl"));
}
else {
children[WORD_ILVL] = NULL;
}
}
else {
children[WORD_NUMID] = NULL;
children[WORD_ILVL] = NULL;
}
}
replaceChildrenFromArray(concrete,children,WordNumPr_Children);
CSSPropertiesRelease(oldp);
}
void WordGetPPr(DFNode *pPr, CSSProperties *properties, const char **styleId, WordSection *section)
{
assert(pPr->tag == WORD_PPR);
if (styleId != NULL)
*styleId = NULL;
for (DFNode *child = pPr->first; child != NULL; child = child->next) {
switch (child->tag) {
case WORD_PSTYLE:
if (styleId != NULL)
*styleId = DFGetAttribute(child,WORD_VAL);
break;
case WORD_PBDR:
WordGetPBdr(child,properties);
break;
case WORD_SHD:
WordGetShd(child,properties);
break;
case WORD_JC: {
const char *val = DFGetAttribute(child,WORD_VAL);
if (val != NULL) {
if (!strcmp(val,"left") || !strcmp(val,"start"))
CSSPut(properties,"text-align","left");
else if (!strcmp(val,"right") || !strcmp(val,"end"))
CSSPut(properties,"text-align","right");
else if (!strcmp(val,"center"))
CSSPut(properties,"text-align","center");
else if (!strcmp(val,"both"))
CSSPut(properties,"text-align","justify");
}
break;
}
case WORD_IND: {
if ((section == NULL) || (WordSectionContentWidth(section) <= 0))
break;
// Units: 1/20th of a point
const char *firstLine = DFGetAttribute(child,WORD_FIRSTLINE);
const char *hanging = DFGetAttribute(child,WORD_HANGING);
const char *left = DFGetAttribute(child,WORD_START);
if (left == NULL)
left = DFGetAttribute(child,WORD_LEFT);;
const char *right = DFGetAttribute(child,WORD_END);
if (right == NULL)
right = DFGetAttribute(child,WORD_RIGHT);
if (left != NULL) {
double leftPct = 100.0*atoi(left)/(double)WordSectionContentWidth(section);
char buf[100];
CSSPut(properties,"margin-left",DFFormatDoublePct(buf,100,leftPct));
}
if (right != NULL) {
double rightPct = 100.0*atoi(right)/(double)WordSectionContentWidth(section);
char buf[100];
CSSPut(properties,"margin-right",DFFormatDoublePct(buf,100,rightPct));
}
// hanging and firstLine attributes are mutually exclusive. If both are specified,
// hanging takes precedence
if (hanging != NULL) {
double indentPct = -100.0*atoi(hanging)/(double)WordSectionContentWidth(section);
char buf[100];
CSSPut(properties,"text-indent",DFFormatDoublePct(buf,100,indentPct));
}
else if (firstLine != NULL) {
double indentPct = 100.0*atoi(firstLine)/(double)WordSectionContentWidth(section);
char buf[100];
CSSPut(properties,"text-indent",DFFormatDoublePct(buf,100,indentPct));
}
break;
}
case WORD_SPACING: {
const char *before = DFGetAttribute(child,WORD_BEFORE);
if (before != NULL) { // units: 1/20th of a point
char buf[100];
CSSPut(properties,"margin-top",DFFormatDoublePt(buf,100,atoi(before)/20.0));
}
const char *after = DFGetAttribute(child,WORD_AFTER);
if (after != NULL) { // units: 1/20th of a point
char buf[100];
CSSPut(properties,"margin-bottom",DFFormatDoublePt(buf,100,atoi(after)/20.0));
}
const char *line = DFGetAttribute(child,WORD_LINE);
const char *lineRule = DFGetAttribute(child,WORD_LINERULE);
if ((lineRule == NULL) || !strcmp(lineRule,"auto") || !strcmp(lineRule,"exact")) {
// Only other alternative for lineRule is "atLeast", which we ignore
if (line != NULL) { // units: 1/2.4th of a percent
char buf[100];
CSSPut(properties,"line-height",DFFormatDoublePct(buf,100,atoi(line)/2.4));
}
}
break;
}
}
}
}
void WordPutPPr(DFNode *pPr, CSSProperties *properties, const char *styleId, WordSection *section, int outlineLvl)
{
assert(pPr->tag == WORD_PPR);
// The child elements of pPr have to be in a specific order. So we build up a structure based
// on the existing elements (updated as appropriate from newProperties), and then rebuild
// from that
CSSProperties *oldp = CSSPropertiesNew();
CSSProperties *newp = properties;
const char *oldStyleId = NULL;
const char *newStyleId = styleId;
WordGetPPr(pPr,oldp,&oldStyleId,section);
{
DFNode *children[PREDEFINED_TAG_COUNT];
childrenToArray(pPr,children);
int existingOutlineLvl = -1;
if (children[WORD_OUTLINELVL] != NULL) {
const char *value = DFGetAttribute(children[WORD_OUTLINELVL],WORD_VAL);
if (value != NULL)
existingOutlineLvl = atoi(value);
}
// Style name
if (!DFStringEquals(oldStyleId,newStyleId)) {
if (newStyleId != NULL) {
children[WORD_PSTYLE] = DFCreateElement(pPr->doc,WORD_PSTYLE);
DFSetAttribute(children[WORD_PSTYLE],WORD_VAL,newStyleId);
}
else {
children[WORD_PSTYLE] = NULL;
}
}
// Paragraph border (pBdr)
if (children[WORD_PBDR] == NULL)
children[WORD_PBDR] = DFCreateElement(pPr->doc,WORD_PBDR);
WordPutPBdr(children[WORD_PBDR],oldp,newp);
if (children[WORD_PBDR]->first == NULL)
children[WORD_PBDR] = NULL;
// Numbering and outline level
// Don't change these properties for styles with outline level >= 6, as these can't be
// represented with the standard HTML heading elements, which only go from h1 - h6
// (outline levels 0 - 5)
if (existingOutlineLvl < 6) {
if (children[WORD_NUMPR] == NULL)
children[WORD_NUMPR] = DFCreateElement(pPr->doc,WORD_NUMPR);
WordPutNumPr(children[WORD_NUMPR],newp);
if (children[WORD_NUMPR]->first == NULL)
children[WORD_NUMPR] = NULL;
if ((outlineLvl >= 0) && (outlineLvl <= 5)) {
if (children[WORD_OUTLINELVL] == NULL)
children[WORD_OUTLINELVL] = DFCreateElement(pPr->doc,WORD_OUTLINELVL);
DFFormatAttribute(children[WORD_OUTLINELVL],WORD_VAL,"%d",outlineLvl);
}
else {
children[WORD_OUTLINELVL] = NULL;
}
}
// background-color
char *oldBackgroundColor = CSSHexColor(CSSGet(oldp,"background-color"),0);
char *newBackgroundColor = CSSHexColor(CSSGet(newp,"background-color"),0);
if (!DFStringEquals(oldBackgroundColor,newBackgroundColor))
WordPutShd(pPr->doc,&children[WORD_SHD],newBackgroundColor);
free(oldBackgroundColor);
free(newBackgroundColor);
// text-align
if (!DFStringEquals(CSSGet(oldp,"text-align"),CSSGet(newp,"text-align"))) {
const char *newTextAlign = CSSGet(newp,"text-align");
if (newTextAlign != NULL) {
const char *val = NULL;
if (!strcmp(newTextAlign,"left"))
val = "left";
else if (!strcmp(newTextAlign,"right"))
val = "right";
else if (!strcmp(newTextAlign,"center"))
val = "center";
else if (!strcmp(newTextAlign,"justify"))
val = "both";
if (val != NULL) {
children[WORD_JC] = DFCreateElement(pPr->doc,WORD_JC);
DFSetAttribute(children[WORD_JC],WORD_VAL,val);
}
}
else {
children[WORD_JC] = NULL;
}
}
if ((section != NULL) && (WordSectionContentWidth(section) >= 0)) {
const char *oldMarginLeft = CSSGet(oldp,"margin-left");
const char *oldMarginRight = CSSGet(oldp,"margin-right");
const char *oldTextIndent = CSSGet(oldp,"text-indent");
const char *newMarginLeft = CSSGet(newp,"margin-left");
const char *newMarginRight = CSSGet(newp,"margin-right");
const char *newTextIndent = CSSGet(newp,"text-indent");
// Special case of the List_Paragraph style, which is used by Word to ensure lists are indented. We
// don't set this property for HTML, because it automatically indents lists. However we need to ensure
// that it remains unchanged when updating the word document
const char *newWordMarginLeft = CSSGet(newp,"-word-margin-left");
if (newMarginLeft == NULL)
newMarginLeft = newWordMarginLeft;
if ((newMarginLeft == NULL) && (newMarginRight == NULL) && (newTextIndent == NULL)) {
children[WORD_IND] = NULL;
}
else {
if (children[WORD_IND] == NULL)
children[WORD_IND] = DFCreateElement(pPr->doc,WORD_IND);
if (!DFStringEquals(oldMarginLeft,newMarginLeft)) {
if (newMarginLeft != NULL)
updateTwipsFromLength(children[WORD_IND],WORD_LEFT,newMarginLeft,WordSectionContentWidth(section));
else
DFRemoveAttribute(children[WORD_IND],WORD_LEFT);
DFRemoveAttribute(children[WORD_IND],WORD_START);
}
if (!DFStringEquals(oldMarginRight,newMarginRight)) {
if (newMarginRight != NULL)
updateTwipsFromLength(children[WORD_IND],WORD_RIGHT,newMarginRight,WordSectionContentWidth(section));
else
DFRemoveAttribute(children[WORD_IND],WORD_RIGHT);
DFRemoveAttribute(children[WORD_IND],WORD_END);
}
if (!DFStringEquals(oldTextIndent,newTextIndent)) {
if (newTextIndent != NULL) {
CSSLength length = CSSLengthFromString(newTextIndent);
if (CSSLengthIsValid(length)) {
double pts = CSSLengthToPts(length,WordSectionContentWidthPts(section));
int twips = (int)round(pts*20);
if (twips >= 0) {
DFFormatAttribute(children[WORD_IND],WORD_FIRSTLINE,"%d",twips);
DFRemoveAttribute(children[WORD_IND],WORD_HANGING);
}
else {
DFFormatAttribute(children[WORD_IND],WORD_HANGING,"%d",-twips);
DFRemoveAttribute(children[WORD_IND],WORD_FIRSTLINE);
}
}
}
else {
DFRemoveAttribute(children[WORD_IND],WORD_FIRSTLINE);
DFRemoveAttribute(children[WORD_IND],WORD_HANGING);
}
}
}
}
if (!DFStringEquals(CSSGet(oldp,"margin-top"),CSSGet(newp,"margin-top")) ||
!DFStringEquals(CSSGet(oldp,"margin-bottom"),CSSGet(newp,"margin-bottom")) ||
!DFStringEquals(CSSGet(oldp,"line-height"),CSSGet(newp,"line-height"))) {
if ((CSSGet(newp,"margin-top") == NULL) &&
(CSSGet(newp,"margin-bottom") == NULL) &&
(CSSGet(newp,"line-height") == NULL)) {
children[WORD_SPACING] = NULL;
}
else {
children[WORD_SPACING] = DFCreateElement(pPr->doc,WORD_SPACING);
if (DFStringEquals(CSSGet(newp,"margin-top"),"-word-auto")) {
DFSetAttribute(children[WORD_SPACING],WORD_BEFORE,"100");
DFSetAttribute(children[WORD_SPACING],WORD_BEFOREAUTOSPACING,"1");
}
else {
char *before = twipsFromCSS(CSSGet(newp,"margin-top"),WordSectionContentWidth(section));
DFSetAttribute(children[WORD_SPACING],WORD_BEFORE,before);
DFSetAttribute(children[WORD_SPACING],WORD_BEFOREAUTOSPACING,NULL);
free(before);
}
if (DFStringEquals(CSSGet(newp,"margin-bottom"),"-word-auto")) {
DFSetAttribute(children[WORD_SPACING],WORD_AFTER,"100");
DFSetAttribute(children[WORD_SPACING],WORD_AFTERAUTOSPACING,"1");
}
else {
char *after = twipsFromCSS(CSSGet(newp,"margin-bottom"),WordSectionContentWidth(section));
DFSetAttribute(children[WORD_SPACING],WORD_AFTER,after);
DFSetAttribute(children[WORD_SPACING],WORD_AFTERAUTOSPACING,NULL);
free(after);
}
CSSLength lineHeight = CSSLengthFromString(CSSGet(newp,"line-height"));
if (CSSLengthIsValid(lineHeight) && (lineHeight.units == UnitsPct)) {
int value = (int)round(lineHeight.value*2.4);
DFFormatAttribute(children[WORD_SPACING],WORD_LINE,"%d",value);
}
if (children[WORD_SPACING]->attrsCount == 0)
children[WORD_SPACING] = NULL;
}
}
replaceChildrenFromArray(pPr,children,WordPPR_Children);
}
CSSPropertiesRelease(oldp);
}