----------------------------------------------------------------------- -- ABW exporter in Lua for SciTE -- Version 0.9.3, 20070805 -- -- Copyright 2005-2007 by Kein-Hong Man -- All Rights Reserved -- -- Permission to use, copy, modify, and distribute this software and -- its documentation for any purpose and without fee is hereby granted, -- provided that the above copyright notice appear in all copies and -- that both that copyright notice and this permission notice appear -- in supporting documentation. -- -- KEIN-HONG MAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -- INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN -- NO EVENT SHALL KEIN-HONG MAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR -- CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -- OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION -- WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- ----------------------------------------------------------------------- -- USAGE -- -- * Style hierarchy is as follows: the default SciTE style (style 32) -- is based on Normal, and all other styles is based on the default -- SciTE style. So the SciTE style hierarchy is preserved while the -- Normal style is unaffected for normal text editing. -- * If your file uses UTF-8, then they are passed verbatim to AbiWord, -- by changing UTF-8 sequences into &#xhhhh; entities. No error -- correction is done, so it is up to the user to ensure correctness. -- * UCS-2 currently not handled, while 8-bit values >127 are simply -- converted to entities without any specific charset conversion. You -- might need to use a charset converter to do things correctly. -- * Control characters are shown as numbers in << >> delimiters. -- (Same mechanism as SXW exporter.) -- ----------------------------------------------------------------------- -- OPTIONS -- -- * Paste them into your SciTEUser.properties file or equivalent in -- order to make adjustments to the output. The property values given -- are the script defaults. -- * In the "configurable stuff" section in this script: -- (1) Table ABW.FONT_SUBSTITUTE allows you to substitute fonts -- (2) Table ABW.PAPER_LIST is a table of paper sizes -- -- * These are 0/1-style false/true properties: --[[ # if 1, use document's own tab width to format output export.abw.doctabs=1 # if 1, convert tabs to spaces (width in "tabsize" property) export.abw.converttabs=1 # if 1, explicitly specify whites (for opaque whites) export.abw.transparent=1 # if 1, output used styles only export.abw.styleused=1 # if 1, enable background colouring export.abw.background=1 # if 0, disables font size, bold, italics, underline styles # to disable fonts, use export.abw.monofont export.abw.wysiwyg=1 # if 1, enable colours in exported documents export.abw.colour=1 # if 0, don't apply a text style to whitespace-only segments export.abw.spacestyled=0 ]] -- -- * These are properties that require values: --[[ # magnification (added to screen font size) export.abw.magnification=0 # if a font is specified, this overrides all other fonts, can be # used as a monospace font mode enabler export.abw.monofont=Courier New # portrait or landscape page orientation export.abw.orientation=portrait # type of paper or a "width, height" pair with proper units: # export.abw.pagesize=210.0mm,297.0mm # or a paper name from a list (case-insensitive): export.abw.pagesize=A4 # margins: "top, bottom, left, right", with proper units # units are usually in "in" or "mm" export.abw.margins=1.0in,1.0in,1.0in,1.0in ]] ----------------------------------------------------------------------- -- a simple check to alert of namespace collision, but allows different -- files to define their own functions in the exporters table ----------------------------------------------------------------------- if exporters then if exporters.SaveToABW then error("SciTE_ExporterABW: exporters.SaveToABW already defined") end -- set pass-through if charset translation not initialized if not exportutil.CharsetFromSet then exportutil.CharsetFromSet = function(c) return c end end else exporters = {} -- create table end ----------------------------------------------------------------------- -- configurable stuff ----------------------------------------------------------------------- local ABW = {} -- page settings defaults (please override using SciTE properties) ABW.TABSIZE = 4 ABW.PAPER = "A4" ABW.ORIENTATION = "portrait" ABW.MARGINS = "1.0in,1.0in,1.0in,1.0in" -- these are place here for easy editing ABW.GENERATOR = "SciTE" ABW.DC_FORMAT ="application/x-abiword" ----------------------------------------------------------------------- -- font substitution table ----------------------------------------------------------------------- ABW.FONT_SUBSTITUTE = { --["Verdana"] = "Arial", -- example: switch all Verdana to Arial --[""] = "", } ----------------------------------------------------------------------- -- paper name substitution table -- Sizes are from fp_PageSize.cpp in the AbiWord sources, RH9 distro. -- The sources are quite old, this list may have changed since then. -- Margin values were removed; they are more useful for envelopes. -- * uncomment more if you need unusual sizes ----------------------------------------------------------------------- ABW.PAPER_LIST = { -- the A sizes --["4a"] = "1682.0, 2378.0, mm", --["2a"] = "1189.0, 1682.0, mm", --["a0"] = "841.0, 1189.0, mm", --["a1"] = "594.0, 841.0, mm", --["a2"] = "420.0, 594.0, mm", ["a3"] = "297.0, 420.0, mm", ["a4"] = "210.0, 297.0, mm", ["a5"] = "148.0, 210.0, mm", --["a6"] = "105.0, 148.0, mm", --["a7"] = "74.0, 105.0, mm", --["a8"] = "52.0, 74.0, mm", --["a9"] = "37.0, 52.0, mm", --["a10"] = "26.0, 37.0, mm", -- the B sizes --["4b"] = "2000.0, 2828.0, mm", --["2b"] = "1414.0, 2000.0, mm", --["b0"] = "1000.0, 1414.0, mm", --["b1"] = "707.0, 1000.0, mm", --["b2"] = "500.0, 707.0, mm", --["b3"] = "353.0, 500.0, mm", ["b4"] = "250.0, 353.0, mm", ["b5"] = "176.0, 250.0, mm", ["b6"] = "125.0, 176.0, mm", --["b7"] = "88.0, 125.0, mm", --["b8"] = "62.0, 88.0, mm", --["b9"] = "44.0, 62.0, mm", --["b10"] = "31.0, 44.0, mm", -- the C sizes --["c0"] = "917.0, 1297.0, mm", --["c1"] = "648.0, 917.0, mm", --["c2"] = "458.0, 648.0, mm", ["c3"] = "324.0, 458.0, mm", ["c4"] = "229.0, 324.0, mm", -- FIXME: C5 is dealt with below, under envelopes -- FIXME: should prolly have C6/C5 here too, for completeness ["c6"] = "114.0, 162.0, mm", --["c7"] = "81.0, 114.0, mm", --["c8"] = "57.0, 81.0, mm", --["c9"] = "40.0, 57.0, mm", --["c10"] = "28.0, 40.0, mm", -- Japanese B sizes -- FIXME: should prolly have the other Japanese sizes ["b5-japan"] = "182.0, 258.0, mm", -- the rest ["legal"] = "8.5, 14.0, in", ["folio"] = "8.5, 13.0, in", ["letter"] = "8.5, 11.0, in", ["half-letter"] = "8.5, 5.5, in", ["executive"] = "7.5, 10.0, in", ["tabloid/ledger"] = "280.1, 267.0, mm", --["monarch"] = "99.0, 191.0, mm", --["superb"] = "297.0, 433.0, mm", --["envelope-commercial"] = "105.0, 242.0, mm", --["envelope-monarch"] = "99.0, 191.0, mm", --["envelope-dl"] = "110.0, 220.0, mm", --["envelope-c5"] = "162.0, 229.0, mm", --["europostcard"] = "105.0, 148.0, mm", --["custom"] = "0.0, 0.0, mm", } ------------------------------------------------------------------------ -- Declarations for pieces of ABW document -- * these were extracted from AbiWord 2.2.2 sample documents -- * table.concat adds a newline to each line, no need extra newlines ------------------------------------------------------------------------ ABW.XML_HEADER = [[ ]] ------------------------------------------------------------------------ -- Comment (unused in this exporter; not required) ------------------------------------------------------------------------ ABW.COMMENT = [[ ]] ------------------------------------------------------------------------ -- Main enclosing body tag of ABW document ------------------------------------------------------------------------ ABW.ABIWORD_TAG = [[ ]] ABW.STYLE_NORMAL = [[ ]] ABW.S_TAG = [[ ]] -- broken into multiple lines for easier editing ABW.PROPS_NORMAL = "text-indent:0in; margin-top:0pt; margin-left:0pt; font-stretch:normal; ".. "line-height:1.000000; text-align:left; font-variant:normal; lang:en-US; ".. "dom-dir:ltr; margin-bottom:0pt; text-decoration:none; font-weight:normal; ".. "bgcolor:transparent; color:000000; text-position:normal; font-size:12pt; ".. "margin-right:0pt; font-style:normal; widows:2; font-family:Times New Roman" ABW.PAGESIZE_TAG = [[ ]] ABW.SECTION_TAG = [[
]] ----------------------------------------------------------------------- -- exporters:SaveToABW -- -- Exports the document in the current window to an ABW format file. -- ----------------------------------------------------------------------- function exporters:SaveToABW() --------------------------------------------------------------------- -- XML standard entities (always present; part of XML standard) --------------------------------------------------------------------- local entities = { ["&"] = "&", ["<"] = "<", [">"] = ">", -- AbiWord does not escape these; they can be optionally enabled --["'"] = "'", ["\""] = """, } --------------------------------------------------------------------- -- text selection --------------------------------------------------------------------- local selBeg, selEnd = exportutil.GetSelPos() editor:Colourise(0, -1) --------------------------------------------------------------------- -- UTF-8 encoding flag (UCS-2 currently not handled), boolean --------------------------------------------------------------------- local UTF8_ON = editor.CodePage == SC_CP_UTF8 --------------------------------------------------------------------- -- tab to space value --------------------------------------------------------------------- local tabSize = tonumber(props["tabsize"]) if not tabSize or tabSize <= 0 or tabSize >= 99 then tabSize = ABW.TABSIZE end if exportutil.propbool("export.abw.doctabs", 1) then tabSize = editor.TabWidth end --------------------------------------------------------------------- -- boolean flags --------------------------------------------------------------------- local convertTabs = exportutil.propbool("export.abw.converttabs", 1) local transparent = exportutil.propbool("export.abw.transparent", 1) local stylesUsed = exportutil.propbool("export.abw.styleused", 1) local background = exportutil.propbool("export.abw.background", 1) local wysiwyg = exportutil.propbool("export.abw.wysiwyg", 1) local colour = exportutil.propbool("export.abw.colour", 1) local spaceStyled = exportutil.propbool("export.abw.spacestyled") --------------------------------------------------------------------- -- valued properties --------------------------------------------------------------------- local page = {} --------------------------------------------------------------------- -- returns comma-separated fields in a property value local function parseProp(propValue) local propList = {} if not propValue or propValue == "" then return propList end for v in string.gfind(propValue, "([^,]+),?") do table.insert(propList, v) end return unpack(propList) end --------------------------------------------------------------------- -- retrieve units from a length string local function getUnit(v) local _, _, num, unit = string.find(v, "^(%A+)(%a+)$") return num, unit end --------------------------------------------------------------------- -- magnification value to add to screen font size local magnification = tonumber(props["export.abw.magnification"]) or 0 -- font override property local monoFont = props["export.abw.monofont"] if not monoFont or monoFont == "" then monoFont = nil end --------------------------------------------------------------------- -- page orientation page.orientation = string.lower(props["export.abw.orientation"]) if not page.orientation or page.orientation == "" then if page.orientation ~= "portrait" and page.orientation ~= "landscape" then page.orientation = ABW.ORIENTATION end end --------------------------------------------------------------------- -- paper size: paper name or width, height page.type = string.lower(props["export.abw.pagesize"]) local pageInfo = ABW.PAPER_LIST[page.type] if pageInfo or (not page.type or page.type == "") then -- set to default size if paper name not specified if not pageInfo then page.type = ABW.PAPER pageInfo = ABW.PAPER_LIST[string.lower(page.type)] end -- extract preset paper information _, _, page.height, page.width, page.units = string.find(pageInfo, "^(%S+)%s*,%s*(%S+)%s*,%s*(%a+)$") -- swap width & height if landscape if page.orientation == "landscape" then page.height, page.width = page.width, page.height end else -- no paper name, assume custom, get size from properties page.width, page.height = parseProp(page.type) if not page.width or not page.height then error("custom page size not specified properly in export.abw.pagesize") end -- split units and make sure they are identical page.width, page.u1 = getUnit(page.width) page.height, page.u2 = getUnit(page.height) if page.u1 ~= page.u2 then error("units must match for lengths in export.abw.pagesize") end page.units = page.u1 page.type = "Custom" end --------------------------------------------------------------------- -- page margins: top, bottom, left, right page.top, page.bottom, page.left, page.right = parseProp(props["export.abw.margins"]) if not page.top or not page.bottom or not page.left or not page.right then page.top, page.bottom, page.left, page.right = parseProp(ABW.MARGINS) end -- fill in dummy values for header and footer margins page.header = page.top page.footer = page.bottom --------------------------------------------------------------------- -- handle font substitution --------------------------------------------------------------------- local function font_actual(font) local subst = ABW.FONT_SUBSTITUTE[font] if subst then return subst end return font end --------------------------------------------------------------------- -- dump all styles or only styles that are used --------------------------------------------------------------------- local styleIsUsed = {} if stylesUsed then for i = selBeg, selEnd - 1 do styleIsUsed[exportutil.StyleAt(i)] = true end else for i = 0, exportutil.STYLE_MAX do styleIsUsed[i] = true end end styleIsUsed[exportutil.STYLE_DEFAULT] = true --------------------------------------------------------------------- -- style handling --------------------------------------------------------------------- stylemgr:setlexer(editor.Lexer) -- helper functions for processing style information into XML specs local function IsSpecified(sd, field) return (string.find(sd.specified, field, 1, 1)) end local function AddProp(style, prop) if style == "" then return prop end return style.."; "..prop end local function HexStr(colourSpec) local hex = string.sub(stylemgr:hexstr(colourSpec), 2) if transparent and hex == "FFFFFF" then return "transparent" end return hex end -- build style specifications local style = {} local styleName = {} local stylePrefix = (stylemgr:getlexerlanguage() or "txt").."_S" for i = 0, exportutil.STYLE_MAX do if styleIsUsed[i] then -- read and process style entry local sd = stylemgr:locate(i) styleName[i] = [[]] -- start of style specification local st = "" if sd.specified ~= "" then -- handle font naming and monofont override if IsSpecified(sd, "font") then local actual_font if not monoFont then actual_font = font_actual(sd.font) elseif monoFont and i == exportutil.STYLE_DEFAULT then actual_font = font_actual(monoFont) end if actual_font then st = AddProp(st, "font-family:"..actual_font) end end -- handle font properties optionally if wysiwyg or i == exportutil.STYLE_DEFAULT then if IsSpecified(sd, "size") then sd.size = sd.size + magnification st = AddProp(st, "font-size:"..sd.size.."pt") end end if wysiwyg then if IsSpecified(sd, "italics") then local v = sd.italics and "italic" or "normal" st = AddProp(st, "font-style:"..v) end if IsSpecified(sd, "bold") then local v = sd.bold and "bold" or "normal" st = AddProp(st, "font-weight:"..v) end if IsSpecified(sd, "underlined") then local v = sd.underlined and "underline" or "none" st = AddProp(st, "text-decoration:"..v) end end--if wysiwyg --if IsSpecified(sd, "eolfilled") and sd.eolfilled then -- TODO low priority; is this even possible? --end -- handle foreground and background optionally if colour then local c = HexStr(sd.fore) if IsSpecified(sd, "fore") then st = AddProp(st, "color:"..c) end c = HexStr(sd.back) if background and IsSpecified(sd, "back") then st = AddProp(st, "bgcolor:"..c) end end--if colour end -- determine style hierarchy: Normal -> style 32 -> other styles local basedon = stylePrefix..(exportutil.STYLE_DEFAULT) if i == exportutil.STYLE_DEFAULT then basedon = "Normal" end -- end of style specification, build actual XML line style[i] = string.format(ABW.S_TAG, stylePrefix..i, basedon, st) end--if styleIsUsed end--for i --------------------------------------------------------------------- -- open exported file for saving --------------------------------------------------------------------- local saveName = exportutil.exportfile(props["FileDir"], props["FileName"], "abw") local fp = io.open(saveName, "wb") if not fp then error("exporters:SaveToABW: could not save file \""..saveName.."\"") end if exportutil.VERBOSE ~= 0 then _ALERT("\nExporting to ABW, filepath: "..saveName) end --------------------------------------------------------------------- -- build file data --------------------------------------------------------------------- -- builds a document meta information entry local function make_meta(key, value) return [[]]..value..[[]] end local data = {} --------------------------------------------------------------------- -- xml file header table.insert(data, ABW.XML_HEADER) table.insert(data, ABW.ABIWORD_TAG) --table.insert(data, ABW.COMMENT) -- not necessary --------------------------------------------------------------------- -- metadata section table.insert(data, [[]]) table.insert(data, make_meta("dc.format", ABW.DC_FORMAT)) table.insert(data, make_meta("abiword.generator", ABW.GENERATOR)) table.insert(data, make_meta("abiword.date_last_changed", os.date("%a %b %d %H:%M:%S %Y"))) -- actually %C but for some reason fails -- on my SciTE build on a Win32 machine -- (probably MSVCRT incompatibility) table.insert(data, [[]]) --------------------------------------------------------------------- -- style section table.insert(data, [[]]) table.insert(data, string.format(ABW.STYLE_NORMAL, ABW.PROPS_NORMAL)) for _,styleItem in pairs(style) do table.insert(data, styleItem) end table.insert(data, [[]]) --------------------------------------------------------------------- -- page information section table.insert(data, string.format(ABW.PAGESIZE_TAG, page.type, page.orientation, page.width, page.height, page.units)) table.insert(data, string.format(ABW.SECTION_TAG, page.footer, page.header, page.right, page.left, page.top, page.bottom)) --------------------------------------------------------------------- -- main loop --------------------------------------------------------------------- local line = {} local seg = ""; local notwhitespace = false local column = 0 local i = selBeg local styleCurrent --------------------------------------------------------------------- -- apply style to a line segment --------------------------------------------------------------------- function styleSegment(segment) if notwhitespace or spaceStyled then return styleName[styleCurrent]..segment..[[]] end return segment end while i < selEnd do local ch = exportutil.CharAt(i) local styleNow = exportutil.StyleAt(i) ------------------------------------------------------------------- -- handle newlines ------------------------------------------------------------------- if ch == "\n" or ch == "\r" then if ch == "\r" and exportutil.CharAt(i + 1) == "\n" then i = i + 1 end table.insert(line, styleSegment(seg)) table.insert(line, [[

]]) table.insert(data, table.concat(line)) column = 0 line = {}; styleCurrent = nil seg = ""; notwhitespace = false end ------------------------------------------------------------------- -- handle style change (always true at beginning of line) ------------------------------------------------------------------- if styleNow ~= styleCurrent then -- close previous text span if required if column > 0 then table.insert(line, styleSegment(seg)) else -- start of line here line = {[[

]]} end seg = ""; notwhitespace = false styleCurrent = styleNow end--style ------------------------------------------------------------------- -- handle tabs ------------------------------------------------------------------- if ch == "\t" then if convertTabs then local ts = tabSize - column % tabSize seg = seg..string.rep(" ", ts) column = column + ts else seg = seg..ch -- normal tab column = column + 1 end ------------------------------------------------------------------- -- handle control characters ------------------------------------------------------------------- else local c = string.byte(ch) notwhitespace = true if c < 32 then if ch ~= "\r" and ch ~= "\n" then -- ALTERNATIVES control char handling seems to use style 36 -- With the method below, we can still see the value... local ctrl = "«"..c.."»" -- "«"..c.."»" seg = seg..ctrl column = column + string.len(ctrl) end ----------------------------------------------------------------- -- handle characters with codes > 127 (conversion to UTF-8 with -- optional charset conversion) ----------------------------------------------------------------- elseif c > 127 then -- *** -- The following borks on AbiWord 2.4/Win32, so I am just using -- a reverse-engineered alternative where no entities are used. -- *** -- * AbiWord file format document states that an ABW XML file must -- be a 7-bit clean ASCII XML file. So use: "ÿ" or "￿" -- and UTF8 sequences need to be changed into the above. --[[ if UTF8_ON then --convert a 2 or 3 byte UTF8 sequence to an entity local u if c < 224 then u = math.mod(c, 32) * 64 i = i + 1; c = string.byte(exportutil.CharAt(i)) u = u + math.mod(c, 128) else u = math.mod(c, 16) * 4096 i = i + 1; c = string.byte(exportutil.CharAt(i)) u = u + math.mod(c, 128) * 64 i = i + 1; c = string.byte(exportutil.CharAt(i)) u = u + math.mod(c, 128) end ch = string.format("&#x%04x;", u) else --]] if not UTF8_ON then -- convert to UTF-8 c = exportutil.CharsetToUTF8(c) if c >= 2048 then ch = string.char(224 + math.floor(c / 4096), 128 + math.floor(c / 64) % 64, 128 + c % 64) elseif c >= 128 then ch = string.char(192 + math.floor(c / 64), 128 + c % 64) else ch = string.char(c) end end seg = seg..ch column = column + 1 ----------------------------------------------------------------- -- handle normal characters ----------------------------------------------------------------- else seg = seg..(entities[ch] or ch) column = column + 1 end--if c end--if ch i = i + 1 end--while -- clean up if last line has no EOL if seg ~= "" or #line > 0 then table.insert(line, styleSegment(seg)..[[

]]) table.insert(data, table.concat(line)) else table.insert(data, [[

]]) end -- close tags table.insert(data, [[
]]) table.insert(data, [[
]]) --------------------------------------------------------------------- -- write and close exported file --------------------------------------------------------------------- fp:write(table.concat(data, "\n")) fp:close() if exportutil.VERBOSE ~= 0 then exportutil.progress("... done.") end end -- end of script