Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple a:bodyPr tags within a:txBody causes damaged presentation in PowerPoint 2007 #69

Closed
ZouhaierSebri opened this issue Apr 3, 2017 · 12 comments
Assignees
Milestone

Comments

@ZouhaierSebri
Copy link

ZouhaierSebri commented Apr 3, 2017

pptxgenjs-demo.html >> TABLES

image
when i'm using the old code of genXmlTextBody all is working good

function genXmlTextBody(slideObj) {
	if ( !slideObj.options ) slideObj.options = {};

	var tagStart = ( slideObj.options.isTableCell ? '<a:txBody>'  : '<p:txBody>' );
	var tagClose = ( slideObj.options.isTableCell ? '</a:txBody>' : '</p:txBody>' );
	// NOTE: OOXML uses the unicode character set for Bullets
	// EX: Unicode Character 'BULLET' (U+2022) ==> '<a:buChar char="&#x2022;"/>'
	var strXmlBullet = '';
	var paraPropXmlCore = '<a:pPr ';
	var paragraphPropXml = '<a:pPr ';
	var strSlideXml = tagStart;

	// STEP 1: Build paragraphProperties
	{
		// OPTION: align
		if ( slideObj.options.align || (Array.isArray(slideObj.text) && slideObj.text[0].options && slideObj.text[0].options.align) ) {
			var align = (Array.isArray(slideObj.text) && slideObj.text[0].options && slideObj.text[0].options.align ? slideObj.text[0].options.align : slideObj.options.align);

			switch ( align ) {
				case 'r':
				case 'right':
					paragraphPropXml += 'algn="r"';
					break;
				case 'c':
				case 'ctr':
				case 'center':
					paragraphPropXml += 'algn="ctr"';
					break;
				case 'justify':
					paragraphPropXml += 'algn="just"';
					break;
			}
		}

		// OPTION: indent
		if ( slideObj.options.indentLevel > 0 ) paragraphPropXml += ' lvl="' + slideObj.options.indentLevel + '"';
		paraPropXmlCore = paragraphPropXml;

		// OPTION: bullet
		if ( typeof slideObj.options.bullet === 'object' ) {
			if ( slideObj.options.bullet.type ) {
				if ( slideObj.options.bullet.type.toString().toLowerCase() == "number" ) {
					paragraphPropXml += ' marL="342900" indent="-342900"';
					strXmlBullet = '<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="arabicPeriod"/>';
				}
			}
			else if ( slideObj.options.bullet.code ) {
				var bulletCode = '&#x'+ slideObj.options.bullet.code +';';

				// Check value for hex-ness (s/b 4 char hex)
				if ( /^[0-9A-Fa-f]{4}$/.test(slideObj.options.bullet.code) == false ) {
					console.warn('Warning: `bullet.code should be a 4-digit hex code (ex: 22AB)`!');
					bulletCode = BULLET_TYPES['DEFAULT'];
				}

				paragraphPropXml += ' marL="342900" indent="-342900"';
				strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="'+ bulletCode +'"/>';
			}
		}
		// DEPRECATED: old bool value (FIXME:Drop in 2.0)
		else if ( slideObj.options.bullet == true ) {
			paragraphPropXml += ' marL="228600" indent="-228600"';
			strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="'+ BULLET_TYPES['DEFAULT'] +'"/>';
		}

		// Close Paragraph-Properties --------------------
		paragraphPropXml += '>'+ strXmlBullet +'</a:pPr>';
	}

	// STEP 2: Build paragraph(s) and its text props/runs =================================
	if ( slideObj.options.bullet
		&& typeof slideObj.text == 'string' && slideObj.text.split('\n').length > 0
		&& slideObj.text.split('\n')[1] && slideObj.text.split('\n')[1].length > 0
	) {
		strSlideXml += genXmlBodyProperties(slideObj.options) + '<a:lstStyle/>';
		// IMPORTANT: Split text here (even though `genXmlTextRun` handles \n) b/c we need `outStyles` for bullets to build correctly
		slideObj.text.toString().split('\n').forEach(function(line,idx){
			if ( idx > 0 ) strSlideXml += '</a:p>';
			strSlideXml += '<a:p>' + paragraphPropXml;
			strSlideXml += genXmlTextRun(slideObj.options, line);
		});
	}
	else if ( typeof slideObj.text == 'string' || typeof slideObj.text == 'number' ) {
		strSlideXml += genXmlBodyProperties(slideObj.options) + '<a:lstStyle/><a:p>' + paragraphPropXml;
		strSlideXml += genXmlTextRun(slideObj.options, slideObj.text.toString());
	}
	else if ( slideObj.text && Array.isArray(slideObj.text) ) {
		// DESC: This is an array of text objects: [{},{}]
		// EX: slide.addText([ {text:'hello', options:{color:'0088CC'}}, {text:'world', options:{color:'CC8800'}} ]);
		strSlideXml += genXmlBodyProperties(slideObj.options) + '<a:lstStyle/>';
		slideObj.text.forEach(function(textObj,idx){
			// A: Options
			textObj.options = textObj.options || {};
			textObj.options.lineIdx = idx;
			// Inherit any main options (color, font_size, etc.)
			// We only pass the text.options below and not the Slide.options, so the run building function cant just fallback to Slide.color,
			// therefore, we need to do that here before passing options below!
			// NOTE: This loop will pick up `x` etc, but it doesnt hurt anything
			$.each(slideObj.options, function(key,val){
				if ( !textObj.options[key] ) textObj.options[key] = val;
			});

			// B: Add a line break if prev line was bulleted and this one isnt, otherwise, this new line will continue on prev bullet line and users probably dont want that!
			if ( idx > 0 && slideObj.text[idx-1].options.bullet && !textObj.options.bullet ) strSlideXml += '</a:p><a:p>';

			// C: Add bullets in text objects (create a paragraph for each {text} ojbect with bullet:true)
			if ( textObj.options.bullet ) {
				// NOTE: Yes, much of this is the same code as above but bulelts can vary per text line (didnt want a func for this)
				var paraBullPropXml = '';

				// 1: First, handle bullets (whether they're index=0 or not handle them)
				if ( typeof textObj.options.bullet === 'object' ) {
					if ( textObj.options.bullet.type ) {
						if ( textObj.options.bullet.type.toString().toLowerCase() == "number" ) {
							paraBullPropXml = paraPropXmlCore + ' marL="342900" indent="-342900"';
							strXmlBullet = '<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="arabicPeriod"/>';
						}
					}
					else if ( textObj.options.bullet.code ) {
						var bulletCode = '&#x'+ textObj.options.bullet.code +';';
						// Check value for hex-ness (s/b 4 char hex)
						if ( /^[0-9A-Fa-f]{4}$/.test(textObj.options.bullet.code) == false ) {
							console.warn('Warning: `bullet.code should be a 4-digit hex code (ex: 22AB)`!');
							bulletCode = BULLET_TYPES['DEFAULT'];
						}
						paraBullPropXml = paraPropXmlCore + ' marL="342900" indent="-342900"';
						strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="'+ bulletCode +'"/>';
					}
				}
				// DEPRECATED: old bool value (FIXME:Drop in 2.0)
				else if ( textObj.options.bullet == true ) {
					paraBullPropXml = paraPropXmlCore + ' marL="228600" indent="-228600"';
					strXmlBullet = '<a:buSzPct val="100000"/><a:buChar char="&#x2022;"/>';
				}
				// Either close existing <p> (bullets can only be applied at the <p>[level recall], or start th initial <p> if this is the first line)
				strSlideXml += ( idx > 0 ? '</a:p>' : '') + '<a:p>' + paraBullPropXml +'>'+ strXmlBullet +'</a:pPr>';
			}
			// 2: Begin a paragraph if this is the first line
			else if ( idx == 0 ) strSlideXml += '<a:p>' + paragraphPropXml;

			// D: Add formatted textrun
			strSlideXml += genXmlTextRun((textObj.options || slideObj.options), textObj.text.toString());
		});
	}

	// STEP 3: Close paragraphProperties and the current open paragraph
	if ( typeof slideObj.text !== 'undefined' && slideObj.text != null ) {
		strSlideXml += '<a:endParaRPr lang="en-US" '+ ( slideObj.options && slideObj.options.font_size ? ' sz="'+ slideObj.options.font_size +'00"' : '') +' dirty="0"/>';
		strSlideXml += '</a:p>';
	}

	// STEP 4: Close the textBody
	strSlideXml += tagClose;

	// NOTE: If there was no text to format return nothing (or Shape wont render!)
	return ( strSlideXml == tagStart+tagClose ? '' : strSlideXml );
}
@gitbrent
Copy link
Owner

gitbrent commented Apr 3, 2017

Hi @ZouhaierSebri ,

What version of PowerPoint are you using?

@ZouhaierSebri
Copy link
Author

Hi @gitbrent ,
PowerPoint 2007

@gitbrent gitbrent self-assigned this Apr 4, 2017
@gitbrent gitbrent added this to the 1.4.0 milestone Apr 5, 2017
gitbrent pushed a commit that referenced this issue Apr 5, 2017
@gitbrent
Copy link
Owner

gitbrent commented Apr 5, 2017

Hi @ZouhaierSebri ,

Thanks for opening this issue.

The problem was the line breaks being used. While PPT2010+ can digest \n, PPT 2007 cannot.

The solution was to utilize CRLF (\r\n) which is the supported line ending for all versions of PPT 2007+.

Please try your tests again with the current code and let me know...

@gitbrent gitbrent changed the title damaged ppt document Line-breaks result in damaged presentation in PPT 2007 Apr 5, 2017
@ZouhaierSebri
Copy link
Author

ZouhaierSebri commented Apr 5, 2017

the problem persist in pptxgenjs-demo.html >>Shapes & Text

image

@gitbrent
Copy link
Owner

gitbrent commented Apr 5, 2017

Hi @ZouhaierSebri ,

Thanks for testing. I wonder if the cause is the custom bullets? Can you try running the test below via the Demo page sandbox or however you'd like.

var pptx = new PptxGenJS();

pptx.addNewSlide().addText(
  [ { text:'word-level\nformatting', options:{ font_size:36, font_face:'Arial', color:'99ABCC', align:'r' } } ],
  { x:0.5, y:0.5, w:8.5, h:2.5, margin:0.1, fill:'232323' }
);

pptx.addNewSlide().addText(
  [
    { text:'I am a text object with bullets..', options:{bullet:{code:'2605'}, color:'CC0000'} },
    { text:'and I am the next text object.'   , options:{bullet:{code:'25BA'}, color:'00CD00'} },
    { text:'Default bullet text.. '           , options:{bullet:true, color:'696969'} },
    { text:'Final text object w/ bullet:true.', options:{bullet:true, color:'0000AB'} }
  ],
  { x:1.0, y:1.0, w:'50%', h:1.4, color:'ABABAB', margin:1 }
);

pptx.save('PptxGenJS-Sandbox_'+getTimestamp());

@ZouhaierSebri
Copy link
Author

Hi @gitbrent
same result

image

image

@ZouhaierSebri
Copy link
Author

When i change the function
function genXmlTextBody(slideObj)
by an older one (old code already mentionned in my comment) i works like a charme

gitbrent pushed a commit that referenced this issue Apr 6, 2017
@gitbrent
Copy link
Owner

gitbrent commented Apr 6, 2017

Hi @ZouhaierSebri ,

I've re-added some code that was removed form the genXmlTextBody() function.

Please let me know how the test goes now.

@ZouhaierSebri
Copy link
Author

Hi @gitbrent ,
same result the problem persist.
with this code

var pptx = new PptxGenJS();

pptx.addNewSlide().addText(
[
{ text:'I am a text object with bullets..', options:{ color:'CC0000'} },
{ text:'and I am the next text object.' , options:{ color:'00CD00'} }
],
{ x:1.0, y:1.0, w:'50%', h:1.4, color:'ABABAB', margin:1 }
);

pptx.save('PptxGenJS-Sandbox_'+getTimestamp());

I remarked an xml structure difference
New structure

image

Old structure

image

@ZouhaierSebri
Copy link
Author

ZouhaierSebri commented Apr 6, 2017

I propose this solution

// B: Add bodyProp
if ( idx === 0 ) {
		strSlideXml += genXmlBodyProperties(textObj.options) + '<a:lstStyle/>';
        }

and as result

image

gitbrent pushed a commit that referenced this issue Apr 6, 2017
@gitbrent gitbrent changed the title Line-breaks result in damaged presentation in PPT 2007 Multiple a:bodyPr tags within a:txBody causes damaged presentation in PowerPoint 2007 Apr 6, 2017
@gitbrent
Copy link
Owner

gitbrent commented Apr 6, 2017

Hi @ZouhaierSebri ,

Looks like you found the issue!

Having more than one set of <a:bodyPr> and <a:lstStyle/> tags within <a:txBody> causes problems in PPT 2007 and 2010 (Issue #71).

Thank you very much for identifying and patching this issue.

Please retest one more time to verify we've got it fixed now.

@ZouhaierSebri
Copy link
Author

Hi @gitbrent ,
Now it is fixed, thank you for your effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants