Converting QuarkXPress files to CSS (Part 3)

For the past two articles we have been learning how to convert a QuarkXPress document layout to CSS. In part 1 I covered how to convert the document itself and all the picture boxes. In addition I covered a routine for converting RGB color values to their HTML Hexadecimal equivalents. In part 2 I covered representing graphic lines in your QuarkXPress document as CSS. As you saw (if you read part 2) the process for lines was almost identical to picture boxes. In this, the final installment of this series, we will learn how to represent text boxes in CSS. There is very little difference between text boxes and picture boxes and lines. The only significant difference is representing multi-column text boxes. CSS does not allow for multiple columns so we will need to split the text box into a separate DIV for each column. In doing this we will learn how to calculate the gutter widths and new positioning for each individual DIV. This approach will allow us to represent our multi-column text box exactly as it appears in our QuarkXPress document.

If you recall from the previous two articles, we used a routine that retrieved the properties of and sorted our picture boxes (sortPBoxes( ) and pBoxProps( )). We will use the same type of routine to read the properties of text boxes. Because there is so little difference between those and this new routine I’m just going to give you the code witout explanation. The only reason I didn’t combine them all into a single routine in the first place is that I thought it would be easier to just introduce the new concepts with each step instead of spending time going back and modifying existing routines. The Picture Box routines and Text Box routines could, and probably should, be combined into a single routine that handles both. I will leave it up to you to go back and clean up these procedural aspects of the code (Hey, I can’t do everything for you).

Sort the text boxes:

on sortTBoxes(pageNum)
    tell application "QuarkXPress Passport"
        tell document 1
            set theList to {}
            repeat with i from 1 to (count text boxes of page pageNum)
                set myProps to tBoxProps(1, i) of me
                set myProps to convertProps(myProps) of me
                set the end of theList to myProps
            end repeat
            set finalList to {}
            repeat with j from 1 to (length of theList)
                set cols to item 5 of item j of theList
                if cols > 1 then
                    set thisBox to item j of theList
                    set newTBoxes to splitTBoxes(thisBox) of me
                    set finalList to items of finalList & items of newTBoxes
                    set the end of finalList to item j of theList
                end if
            end repeat
            set firstSort to bubbleSort(2, finalList) of me
            set secondSort to bubbleSort(1, firstSort) of me
            return secondSort
        end tell
    end tell
end sortTBoxes

Get text box properties:

on tBoxProps(pageNum, tBoxNum)
    tell application "QuarkXPress Passport"
        tell document 1
            tell page pageNum
                set {x1, y1, x2, y2} to (coerce ((bounds of text box tBoxNum) as list) to list)
                set {x1, y1, x2, y2} to {(coerce x1 to real), (coerce y1 to real), (coerce x2 to real), (coerce y2 to real)}
                if columns of text box tBoxNum > 1 then
                    set cols to columns of text box tBoxNum
                    set theGutter to gutter of text box tBoxNum
                    set theGutter to (coerce theGutter to real)
                    set theGutter to ((theGutter * 72) * newScale)
                    set cols to 1
                    set theGutter to 0
                end if
                set getsImage to false
                set fW to width of frame of text box tBoxNum
                set fW to (coerce fW to real)
                set fc to RGB color value of color of frame of text box tBoxNum
                if name of color of text box tBoxNum is not "None" then
                    set bc to RGB color value of color of text box tBoxNum
                    set bc to {65535, 65535, 65535}
                end if
                return {fW, fc, {x1, y1, x2, y2}, bc, cols, theGutter}
            end tell
        end tell
    end tell
end tBoxProps 

We will focus on the real difference with picture boxes and that is handling multiple columns. The three pieces of information we need are the width of the text box, the number of columns and the gutter width. We will use this information to determine the width, space between and exact locations of our new DIVs representing the columns of the text box. I will concentrate on text boxes that have more than one column but if you notice on line 13 of the sortTBoxes( ) routine, we test for a column count greater than one. If we don’t find one, we simply pass the properties through AS-IS for single-column text boxes. If we encounter a text box with more than one column, we send the properties through to the sortTBoxes( ) routine. Here is that routine:

Split the text boxes:

on splitTBoxes({fW, fc, {x1, y1, x2, y2}, bc, cols, theGutter})
	set newBoxes to {}
	set oldBoxWidth to y2 - y1
	set minusGutters to oldBoxWidth - (theGutter * (cols - 1))
	set newBoxWidth to minusGutters / cols
	-- set theStep to newBoxWidth + theGutter
	repeat with b from 1 to cols
		if b = 1 then
			set newRightEdge to y1 + newBoxWidth
			set thisNewBox to {x1, y1, x2, newRightEdge}
			set nextLeftEdge to newRightEdge + theGutter
			set newLeftEdge to nextLeftEdge
			set newRightEdge to newLeftEdge + newBoxWidth
			set thisNewBox to {x1, newLeftEdge, x2, newRightEdge}
			set nextLeftEdge to newRightEdge + theGutter
		end if
		set allNewProps to {fW, fc, thisNewBox, bc, cols, theGutter}
		set the end of newBoxes to allNewProps
	end repeat
	return newBoxes
end splitTBoxes

The first thing you might notice is that this routine doesn’t make a single call to QuarkXPress. This routine is really just a calculator of sorts. Initially we make a list of all the text boxes on a page in our QuarkXPress document. We will also need a second list ( finalList) because if and when we come across a multi-column text box, we will treat each column as a new text box so our list will become longer. The splitTBoxes( ) routine splits a multi-column text box into a text box for each column and returns however many there are. We append these to our list ( finalList) as we loop through the repeat.

The splitTBoxes( ) routine is really pretty simple. The first thing we need to figure out is how wide our new DIVs will be. Here is the formula used to determine this:

BoxWidth = width of the text box
ColumnCount = number of columns
GutterCount = number of gutters – (one less than number of columns)
GutterWidth = width of the gutter

newDivWidth = (BoxWidth - (GutterCount * GutterWidth))/GutterWidth

Line 3 in the routine subtracts the left bound from the right bound to determine the overall width of the text box. We then add together the width of all the gutters (1 less than the number of columns). The sum of all the gutters is subtracted from the box width then divided by the number of columns. This tells us how wide our new DIVs will be.

What we are doing in the repeat loop is constructing new lists with all the properties we are using for text boxes. The only properties that will change for each DIV, from the original text box, are the bounds. You could look at the process as going from bounds to dimensions and back to bounds.

The if statement on line 8 allows us to treat the first new DIV differently from each successive one. This is because the first DIV will have the same left edge as the original text box. Our bounds { x1, y1, x2, y2} tell us where each of the corner points of the box (or DIV) appear. X is the vertical axis while Y is the horizontal axis. For the first DIV, our X values remain the same. For box 1 the y1 value also remains the same. However, our y2 value needs to be changed to represent the new right edge of the box so we add our newly determined box width to the y1 value to determine our y2 value. We also use this opportunity to determine what the left edge of the next box will be ( nextLeftEdge). This is calculated by adding the gutter width (space between DIVs) to the new right edge of box 1.

The new bounds for box 1 are combined with the other properties of the box and appended to the end of our newBoxes list. On the next loop through the repeat, we fall through to the else part of the if statement. The only real difference from how we treated box 1 is that for each of the remaining boxes both the y1 and y2 values will change. Each new y1 value becomes the right edge of the previous box plus the gutter width. The y2 value is determined by adding the new box width to the new left edge.

That’s all there is to it. Once the newly determined bounds are combined with the other properties and returned to the main portion of the script, everything else proceeds as normal. Each text box is converted to CSS the same way that picture boxes and line boxes are converted.

After adding the three routines above to the script from part one, we only need to add a few more lines of code to see it all working together. Within the main body of the script, add the following line between set boxProperties to sortPBoxes(1) and set LineProps to LineBoxProps(1):

set tBoxProperties to sortTBoxes(1)

Now just before the section that starts:

– Divs for picture boxes
set picSizes to “”
repeat with pBoxNum from 1 to (length of boxProperties)

add the following code:

Make the Text Box Style Sheets:

-- Divs for text boxes
repeat with tBoxNum from 1 to (length of tBoxProperties)
	set myProps to item tBoxNum of tBoxProperties
	set {myStyle, myWidth, myHeight} to boxStyle(tBoxNum, "textbox", myProps, pW, pH, leftOffset, topOffset) of me
	set myCss to myCss & myStyle as string
	set the end of divLst to (("
" & return & "
") as string)
end repeat

You might be wondering, if we started with picture boxes, why are we putting the text box routines first. It’s a good question with a good answer. We want our picture boxes to go in front of any text boxes they might overlap. This is known as the z-index. Remember that the X-Axis is the vertical axis. The Y-Axis is the horizontal axis. The Z-Axis could be thought of as the front to back axis.

And we’re done. I hope I have explained everything well enough for you to follow. I think there can and will be some interesting uses for this script. Just in case I have inadvertently left something out of the explanations or you don’t feel like reconstructing it all from scratch, here is a link to the entire script:Download the script

I’m going to leave it up to you to figure out how to convert the content of your QuarkXPress document. I may revisit this topic in the future but for now we’re going to take a break from it. In the coming weeks I will post an interview with Matt Neuburg, author of AppleScript: The Definitive Guide. I will also post an article on a routine to search blocks of text more quickly.