Word Document Library for the Ring Programming Language
DOCXLib is a pure-Ring library for generating and reading Word (.docx) documents.
- Zero dependencies — Pure Ring
- Fluent API — Every method returns
self, enabling method chaining - Rich tables — Cell merging, per-side borders, row heights, text direction, images, lists inside cells
- Full typography — Bold/italic/colour/font/size per run, language tags, character styles, custom paragraph styles
- Paragraph control — Widow/orphan control, hyphenation suppression, keep-together, contextual spacing
- Navigation — TOC, Table of Figures, Table of Tables, bookmarks, cross-refs
- Forms — Checkboxes, dropdowns, and text input content controls
- Shapes — Rectangle, ellipse, diamond, triangle, line (DrawingML)
- Themes — 8 built-in colour palettes applied to all heading levels
- Charts — 8 native OOXML chart types with axis formatting, data tables, and style presets
- Mail Merge — Template engine:
setMergeTemplate(),mergeRecord(),mergeAll()with{{FIELD}}tokens - Image cropping — Non-destructive
a:srcRectcrop by percentage on inline and floating images - Rich notes — Footnote and endnote bodies with full per-run formatting (bold, italic, colour, size)
- WordReader — Parse
.docxfiles — extract content and styles; full round-trip reconstruct and save
- Installation
- Quick Start
- Document Setup
- Headers, Footers, and Page Numbers
- Text and Paragraphs
- Lists
- Tables
- Images
- Text Watermarks
- Page Borders and Background
- Footnotes and Endnotes
- Hyperlinks, Bookmarks, and Cross-References
- Table of Contents
- Captions and Lists of Figures / Tables
- Tab Stops and Line Numbers
- Comments
- Mail Merge Fields
- Content Controls
- Drawing Shapes
- Text Boxes
- RTL Support
- Saving the Document
- Native OOXML Charts
- Complete Example
- Mail Merge Template Engine
- Chart Data Tables
- Introduction to WordReader
- Loading and Saving
- Document Properties
- Content Query Methods
- Table Query Methods
- Image Query Methods
- Section and Layout Queries
- Round-Trip: toWriter()
- Reader Quick Reference
ringpm install docxlib from ringpackages
load "docxlib.ring"
? "Generate File: hello.docx"
new WordWriter() {
setTitle("My First Document")
setAuthor("Your Name")
setPageSize("a4")
addHeading("Hello, DOCXLib!", 1)
addParagraph("This document was created entirely from Ring code.", [:color = "0000FF"])
addBulletList(["Feature A", "Feature B", "Feature C"])
save("hello.docx")
}Setup methods configure global document properties and should be called before adding content.
| Method | Parameters | Description |
|---|---|---|
setTitle() |
title : String |
Sets the document title (written to core.xml) |
setAuthor() |
author : String |
Sets the document author (written to core.xml) |
setDefaultFont() |
fontName, fontSize |
Body font and size in pt. Defaults: Calibri, 11pt |
addCustomProperty() |
name, value |
Add a custom document property (written to custom.xml) |
| Method | Parameters | Description |
|---|---|---|
setPageSize() |
size : String |
"letter", "a4", "legal", "a3", "b5". Default: letter |
setCustomPageSize() |
widthCm, heightCm |
Exact page dimensions in centimetres |
setOrientation() |
orientation |
"portrait" or "landscape" |
setMargins() |
top, bottom, left, right |
Page margins in centimetres |
setMarginsInches() |
top, bottom, left, right |
Page margins in inches |
setNarrowMargins() |
— | 0.5" margins on all four sides |
setWideMargins() |
— | 1.5" margins on all four sides |
doc.setPageSize("a4")
doc.setMargins(2.54, 2.54, 2.54, 2.54) # 1 inch all sides| Method | Parameters | Description |
|---|---|---|
setColumns() |
numColumns, spaceCm |
Set column count and gap in cm |
setTwoColumns() |
— | Two equal columns, 0.5 cm gap |
setThreeColumns() |
— | Three equal columns, 0.5 cm gap |
addColumnBreak() |
— | Insert a column break at the current position |
addSectionBreak() |
breakType, options |
Insert a section break with optional layout |
addLandscapeStart() |
— | Begin a landscape section |
addLandscapeEnd() |
— | End the landscape section |
addSectionBreak() break types: "continuous", "nextPage", "evenPage", "oddPage".
The second argument is an options list — pass [] when no options are needed:
| Option | Type | Description |
|---|---|---|
:numColumns |
Number | Column count for the new section (2 or more enables multi-column) |
:columnSpaceCm |
Number | Gap between columns in cm |
# Two-column newsletter — begin on same page
doc.addSectionBreak("continuous", [:numColumns=2, :columnSpaceCm=0.6])
doc.addParagraph("Left column content...", [])
doc.addColumnBreak()
doc.addParagraph("Right column content...", [])
# Return to single-column on the next page
doc.addSectionBreak("nextPage", [])
# Landscape page within a portrait document
doc.addLandscapeStart()
doc.addTable(wideData, options)
doc.addLandscapeEnd()setTheme() applies a colour palette to all heading levels and writes word/theme/theme1.xml.
Call it before save().
doc.setTheme("Blue")| Theme | Accent 1 (H1/H2) | Accent 2 (H3/H4) |
|---|---|---|
Office |
#4472C4 Blue | #ED7D31 Orange |
Blue |
#1E6BB5 Deep Blue | #2E75B6 Mid Blue |
Dark |
#88C0D0 Ice Blue | #81A1C1 Steel Blue |
Green |
#375623 Forest | #70AD47 Lime |
Red |
#C00000 Dark Red | #FF0000 Red |
Purple |
#7030A0 Purple | #9B59B6 Violet |
Teal |
#00796B Teal | #009688 Medium Teal |
Warm |
#E65100 Deep Orange | #F57C00 Orange |
defineStyle() registers a named paragraph style. Use the style name via :style in
addParagraph().
doc.defineStyle("CalloutBox", [
:basedOn = "Normal",
:bold = true,
:size = 11,
:color = "1E6BB5",
:bgColor = "DEEAF1",
:align = "center",
:spaceBefore = 120,
:spaceAfter = 120
])
doc.addParagraph("This uses the custom style.", [:style = "CalloutBox"])defineStyle option |
Type | Description |
|---|---|---|
:basedOn |
String | Parent style ID, e.g. "Normal", "Heading1" |
:bold |
true/false | Bold text |
:italic |
true/false | Italic text |
:underline |
true/false | Underlined text |
:font |
String | Font family name |
:size |
Number | Font size in points |
:color |
String | Text colour hex without # |
:align |
String | "left", "center", "right", "both" |
:bgColor |
String | Paragraph background fill colour hex |
:spaceBefore |
Number | Space before in twips (20 twips = 1 pt) |
:spaceAfter |
Number | Space after in twips |
:lineSpacing |
Number | Line spacing multiplier: 1.5, 2.0, etc. |
:indent |
Number | Left indent in twips |
:keepNext |
true/false | Keep with the next paragraph |
:keepLines |
true/false | Prevent splitting across pages |
| Method | Parameters | Description |
|---|---|---|
setHeader() |
text |
Default header text (centred) |
setFooter() |
text |
Default footer text |
setHeaderFooter() |
headerText, footerText |
Set both in one call |
showPageNumbers() |
align |
Add page numbers: "left", "center", "right" |
setFirstPageDifferent() |
bEnable |
Enable a different header/footer on page 1 |
setFirstPageHeader() |
text |
Header text for the first page only |
setFirstPageFooter() |
text |
Footer text for the first page only |
setEvenAndOddHeaders() |
bEnable |
Enable separate headers for even/odd pages |
setEvenPageHeader() |
text |
Header text for even-numbered (left) pages |
setEvenPageFooter() |
text |
Footer text for even-numbered pages |
# Standard header with page numbers
doc.setHeader("Annual Report 2026")
doc.showPageNumbers("right")
# Cover page — no header, subsequent pages with header
doc.setFirstPageDifferent(true)
doc.setFirstPageHeader("") # blank on cover
doc.setHeader("Chapter 1 — Introduction")
# Book-style even/odd headers
doc.setEvenAndOddHeaders(true)
doc.setHeader("Ring Language Study") # odd pages (right side)
doc.setEvenPageHeader("Mahmoud, 2026") # even pages (left side)The primary content method. Pass [] or NULL as the second argument to use all defaults.
doc.addParagraph("Plain text paragraph.", [])
doc.addParagraph("Bold red heading", [:bold=true, :color="C00000", :size=14])
doc.addParagraph("Centred note", [:align="center", :italic=true])
doc.addParagraph("Custom style", [:style="CalloutBox"])| Option | Type | Description |
|---|---|---|
:bold |
true/false | Bold text |
:italic |
true/false | Italic text |
:underline |
true/false | Underlined text |
:size |
Number | Font size in points |
:font |
String | Font family name |
:color |
String | Text colour hex, e.g. "C00000" |
:charStyle |
String | Named character style, e.g. "Strong", "Emphasis" |
:lang |
String | BCP 47 language tag, e.g. "ar-SA", "fr-FR", "de-DE" |
:align |
String | "left", "center", "right", "both" |
:style |
String | Named paragraph style |
:bgColor |
String | Paragraph background colour hex |
:spaceBefore |
Number | Space before in twips (20 twips = 1 pt) |
:spaceAfter |
Number | Space after in twips |
:lineSpacing |
Number | Line spacing multiplier, e.g. 1.5 |
:indent |
Number | Left indent in twips |
:keepNext |
true/false | Keep this paragraph on the same page as the next |
:keepLines |
true/false | Prevent the paragraph from splitting across pages |
:suppressLineNumbers |
true/false | Exclude from line numbering |
:contextualSpacing |
true/false | Suppress spacing between same-style paragraphs |
:widowControl |
false | Set false to disable widow/orphan protection (Word default is on) |
:noHyphenate |
true | Suppress automatic hyphenation on this paragraph |
doc.addHeading("Chapter 1 — Introduction", 1) # Heading 1
doc.addHeading("1.1 Background", 2) # Heading 2
doc.addHeading("Sub-section detail", 3) # Heading 3 (levels 1–6)
doc.addTitle("Document Title") # Title style
doc.addSubtitle("A subtitle line") # Subtitle styleBuild a paragraph from an explicit list of [text, options] run pairs. Each run can
carry its own bold, italic, colour, size, font, language, and character style.
doc.addRichParagraph([
["Status: ", []],
["Passed", [:bold=true, :color="375623"]],
[" — Score: ", []],
["98/100", [:bold=true, :color="1E6BB5", :size=14]]
], [:align="center"])
# Language-tagged runs (Arabic + English in one paragraph)
doc.addRichParagraph([
["مرحبا", [:font="Arial", :lang="ar-SA"]],
[" — Hello", [:lang="en-US"]]
], [])
# Character styles
doc.addRichParagraph([
["See ", []],
["ring-lang.github.io", [:charStyle="Hyperlink"]]
], [])Run-level option keys: :text, :bold, :italic, :underline, :color, :size,
:font, :bgColor, :charStyle, :lang.
| Method | Description |
|---|---|
bold(text) |
Bold paragraph |
italic(text) |
Italic paragraph |
underline(text) |
Underlined paragraph |
colored(text, color) |
Coloured text (hex colour) |
styled(text, font, size, color) |
Single-call full style |
centered(text) |
Centre-aligned paragraph |
rightAligned(text) |
Right-aligned paragraph |
justified(text) |
Justified paragraph |
addBlockQuote(text) |
Indented block-quote paragraph |
addCaption(text) |
Small centred italic caption |
addAbstract(text) |
Indented abstract block with bold "Abstract" heading |
addKeywords(text) |
Keywords line with bold "Keywords:" prefix |
addEmptyParagraph() |
One blank line |
addHorizontalLine() |
Full-width horizontal rule |
addPageBreak() |
Hard page break |
addLineBreak() |
Soft line break within a paragraph |
# Solid background colour
doc.addShadedParagraph("Important note", "FFF3CD", [:bold=true])
# Full border box with optional background
doc.addBorderedParagraph("Critical warning", [
:borderStyle = "single",
:borderColor = "C00000",
:bgColor = "FDECEA",
:spaceBefore = 80,
:spaceAfter = 80,
:indent = 240
])Word protects against widows (last line stranded on the next page) and orphans (first line stranded at the bottom) by default. You can also suppress automatic hyphenation per paragraph — useful for technical terms, URLs, and headings.
# Disable widow/orphan protection for a data-dense paragraph
doc.addParagraph(denseText, [:align="both", :widowControl=false])
# Suppress auto-hyphenation (useful for URLs, product codes, headings)
doc.addParagraph(longTechnicalTerm, [:noHyphenate=true])
# Both together — dense justified paragraph that must not be hyphenated
doc.addParagraph(body, [:align="both", :widowControl=false, :noHyphenate=true])DOCXLib generates proper Word OOXML list numbering — not manually inserted bullet characters.
# Simple bullet list
doc.addBulletList(["Ring language", "Python", "JavaScript"])
# Numbered list
doc.addNumberedList(["Install Ring", "Load docxlib.ring", "Call save()"])
# Nested bullet list — inner list items are sub-lists
doc.addNestedBulletList([
"Programming Languages",
["Ring", "Python", "Java"],
"Databases",
["SQLite", "MySQL"]
])
# Nested numbered list
doc.addNestedNumberedList([
"Phase 1 — Design",
["Requirements", "Architecture"],
"Phase 2 — Development"
])| Method | Parameters | Description |
|---|---|---|
addTable() |
data, options |
Full-featured table with all formatting options |
addSimpleTable() |
data |
Plain table with no header styling |
addStyledTable() |
data, headerBg, evenBg |
Quick styled table with two colour parameters |
| Option | Type | Description |
|---|---|---|
:headerRow |
true/false | First row is a header: bold, coloured background |
:headerBgColor |
String | Header row background colour hex |
:headerTextColor |
String | Header text colour. Default: FFFFFF |
:evenRowBgColor |
String | Alternating even-row background colour |
:borderStyle |
String | "single", "double", "dashed", "dotted", "none" |
:borderColor |
String | Border colour hex. Default: 000000 |
:colWidths |
List | Column widths in cm, e.g. [2, 6, 3, 5.5] |
:headerVAlign |
String | Header vertical alignment: "top", "center", "bottom" |
:cellVAlign |
String | Body cell vertical alignment |
:repeatHeader |
true/false | Repeat header row on each page when the table spans pages |
:rowHeights |
List | Per-row heights in cm — 0 means auto-size, e.g. [1.5, 0, 2.0] |
:rowHRule |
String | Height rule for :rowHeights: "atLeast" (default) or "exact" |
:fontSize |
Number | Font size for all cells in points |
:conditionalRules |
List | Conditional cell formatting rules (see §6.5) |
doc.addTable([
["ID", "Description", "Status", "Owner" ],
["1", "Design new dashboard", "In Progress", "Ahmed" ],
["2", "Fix login timeout on mobile", "Done", "Sara" ],
["3", "Write API documentation v2", "Pending", "Omar" ]
], [
:headerRow = true,
:borderStyle = "single",
:headerBgColor = "1565C0",
:evenRowBgColor = "E3F2FD",
:headerVAlign = "center",
:cellVAlign = "center",
:colWidths = [2, 6, 3, 5.5]
])
# Per-row heights — header 1.5 cm tall, rows 2 & 4 auto-height, row 3 is 2.5 cm
doc.addTable(data, [
:headerRow = true,
:colWidths = [5, 10],
:rowHeights = [1.5, 0, 2.5, 0],
:rowHRule = "atLeast"
])For cells needing mixed formatting, images, lists, or cell spanning:
# Styled cell spanning 3 columns
titleCell = new WordCell()
titleCell.setText("Status Report", [:bold=true, :color="1E6BB5"])
titleCell.setBgColor("DEEAF1")
titleCell.setColSpan(3)
titleCell.setVerticalAlign("center")
# Row-spanning category cell
cat = new WordCell()
cat.addRun("Electronics", [:bold=true, :color="1565C0"])
cat.setRowSpan(3)
cat.setVerticalAlign("center")
cat.setAlign("center")
cat.setBgColor("E3F2FD")
# Convenience function: wordCell(text, options)
hdr = wordCell("Category", [:bold=true, :color="white", :bgColor="37474F"])WordCell method |
Parameters | Description |
|---|---|---|
setText() |
text, options |
Set main text with formatting options |
addRun() |
text, options |
Append an additional text run |
addText() |
text, options |
Alias for addRun() |
addCellBulletList() |
items, listId |
Add a bullet list inside the cell |
addCellNumberedList() |
items, listId |
Add a numbered list inside the cell |
addCellImage() |
path, widthCm, heightCm |
Embed an image in the cell |
addCellHyperlink() |
text, relId |
Add a hyperlink inside the cell |
setBgColor() |
color |
Cell background colour hex |
setColSpan() |
n |
Merge across n columns |
setRowSpan() |
n |
Merge down n rows |
setVerticalAlign() |
align |
"top", "center", "bottom" |
setAlign() |
align |
Text alignment within the cell |
setBorder() |
style, color |
All-sides border (legacy — prefer setBorderSide) |
setBorderSide() |
side, style, color, size |
Per-side border — see §6.4 |
setTopBorder() |
style, color, size |
Top edge only |
setBottomBorder() |
style, color, size |
Bottom edge only |
setLeftBorder() |
style, color, size |
Left edge only |
setRightBorder() |
style, color, size |
Right edge only |
setNoBorder() |
— | Remove all borders from this cell |
setTextDir() |
dir |
Rotate cell text — see §6.4 |
setPadding() |
top, bot, left, right |
Cell padding in cm |
setWidth() |
widthCm |
Override cell width in cm |
Tip: Use
wordCell(text, options)as a shorthand to create aWordCellwith a single text run in one line.
Per-side borders give each cell edge an independent style, colour, and weight. This makes it easy to build accent-left sidebars, coloured header underlines, and borderless inner cells with only outer rules showing.
# Left accent sidebar — only a thick teal left edge, everything else removed
sidebar = wordCell("Note", [])
sidebar.setLeftBorder("thick", "1B998B", 16)
sidebar.setTopBorder("none", NULL, NULL)
sidebar.setBottomBorder("none", NULL, NULL)
sidebar.setRightBorder("none", NULL, NULL)
# Double outer box
boxed = wordCell("Total", [:bold=true])
boxed.setTopBorder("double", "000000", 8)
boxed.setBottomBorder("double", "000000", 8)
boxed.setLeftBorder("double", "000000", 8)
boxed.setRightBorder("double", "000000", 8)
# Header cell with a thick coloured bottom accent only
hdr = wordCell("January", [:bold=true, :align="center"])
hdr.setBottomBorder("thick", "E94F37", 12)setBorderSide(side, style, color, size) parameter values:
| Parameter | Values |
|---|---|
side |
"top", "bottom", "left", "right", "insideH", "insideV" |
style |
"single", "double", "dashed", "dotted", "thick", "none" |
color |
Hex string, or NULL for auto |
size |
Eighths-of-a-point: 4=½pt, 8=1pt, 16=2pt, 24=3pt — or NULL for default |
Text direction rotates the cell's text. Most commonly used for compact column headers in data tables where many narrow columns need rotated labels.
# Text reads upward (BTL) — saves horizontal space for many columns
colHdr = wordCell("January", [:bold=true, :align="center"])
colHdr.setTextDir("btLr")
# Text reads downward (TBR)
colHdr2 = wordCell("Q1 Target", [:bold=true])
colHdr2.setTextDir("tbRl")setTextDir value |
Description |
|---|---|
"btLr" |
Bottom-to-top: text rotated 90° CCW (reads upward) |
"tbRl" |
Top-to-bottom: text rotated 90° CW (reads downward) |
"lrTb" |
Normal horizontal (default — also clears a previous direction) |
Conditional rules apply per-cell background colour, text colour, and bold weight based on the cell's value. Rules are evaluated in order; a later rule overrides an earlier one for the same cell. The header row is never subject to conditional formatting.
Pass rules via the :conditionalRules option in addTable():
| Rule key | Type | Description |
|---|---|---|
:col |
Number | 1-based column index to test; 0 = test all columns |
:condition |
String | "lt", "lte", "gt", "gte", "eq", "neq", "between", "contains" |
:value |
Number / String | Threshold or comparison value |
:value2 |
Number | Upper bound for "between" |
:bgColor |
String | Background colour hex applied when rule matches |
:textColor |
String | Text colour hex applied when rule matches (optional) |
:bold |
true/false | Bold weight applied when rule matches (optional) |
doc.addTable(data, [
:headerRow = true,
:borderStyle = "single",
:colWidths = [4, 3, 3],
:conditionalRules = [
# Column 2 negative values → red background
[:col=2, :condition="lt", :value=0, :bgColor="FDECEA", :textColor="C00000"],
# Column 2 high values → green background
[:col=2, :condition="gte", :value=90, :bgColor="E2EFDA", :textColor="375623"],
# Any column containing "FAIL" → yellow + bold
[:col=0, :condition="contains", :value="FAIL", :bgColor="FFF2CC", :bold=true]
]
])| Method | Parameters | Description |
|---|---|---|
addImage() |
imagePath, widthCm, heightCm |
Inline image, left-aligned |
addImageCentered() |
imagePath, widthCm, heightCm |
Inline image, centred on page |
addImageWithOptions() |
imagePath, widthCm, heightCm, options |
Inline image, left-aligned, with options |
addImageCenteredWithOptions() |
imagePath, widthCm, heightCm, options |
Inline image, centred, with options |
Supported formats: PNG, JPG/JPEG, GIF, BMP.
Image options (used with addImageWithOptions() and addImageCenteredWithOptions()):
| Option | Type | Description |
|---|---|---|
:altText |
String | Accessibility description |
:cropLeft |
Number | Trim left side by this percentage (0–100) |
:cropRight |
Number | Trim right side by this percentage |
:cropTop |
Number | Trim top by this percentage |
:cropBottom |
Number | Trim bottom by this percentage |
# Basic inline image
doc.addImage("logo.png", 5, 3)
doc.addImageCentered("chart.png", 12, 8)
# With options (altText, crop)
doc.addImageCenteredWithOptions("chart.png", 12, 8, [:altText="Benchmark chart"])
# Crop: remove 15% from each side, 10% from top (non-destructive)
doc.addImageCenteredWithOptions("photo.jpg", 12, 8, [
:cropLeft = 15, :cropRight = 15, :cropTop = 10
])
# Centre punch-out — shows only the inner 60×60% of the image
doc.addImageWithOptions("landscape.png", 10, 6, [
:cropLeft=20, :cropRight=20, :cropTop=20, :cropBottom=20
])Note: Cropping is non-destructive. The full image is embedded; only the displayed viewport changes. Uses the OOXML
a:srcRectelement.
addFloatingImage() positions an image independently of the text flow.
doc.addFloatingImage("logo.png", 4, 3, [
:wrapType = "wrapSquare",
:wrapSide = "right",
:posX = 100,
:posY = 200,
:distCm = 0.3,
:altText = "Company logo",
:cropTop = 5,
:cropBottom = 5
])| Method | Parameters | Description |
|---|---|---|
setImageWatermark() |
imagePath, options |
Background image watermark on every page |
removeImageWatermark() |
— | Remove the image watermark |
| Option | Type | Description |
|---|---|---|
:widthCm |
Number | Watermark width in cm. Default: 15 |
:heightCm |
Number | Watermark height in cm. Default: 15 |
:opacity |
Number | Opacity 0–100. Default: 50 |
doc.setImageWatermark("logo_grey.png", [:widthCm=10, :opacity=25])| Method | Parameters | Description |
|---|---|---|
setWatermark() |
text, options |
Diagonal text watermark on every page |
setWatermarkText() |
text |
Change watermark text, keep other settings |
removeWatermark() |
— | Remove the text watermark |
| Option | Type | Description |
|---|---|---|
:color |
String | Hex colour. Default: C0C0C0 |
:opacity |
Number | Opacity 0–100. Default: 50 |
:rotation |
Number | Rotation in degrees (negative = tilt left). Default: –45 |
:font |
String | Font name. Default: Arial |
:size |
Number | Font size in pt. Default: 72 |
# Simple DRAFT watermark
doc.setWatermarkText("DRAFT")
# Custom watermark
doc.setWatermark("CONFIDENTIAL", [
:color = "FF0000",
:opacity = 30,
:rotation = -45,
:size = 96
])| Method | Parameters | Description |
|---|---|---|
setPageBorder() |
options |
Add borders around every page |
setSimplePageBorder() |
— | Single-line 3pt black border on all four sides |
removePageBorder() |
— | Remove the page border |
setPageBackground() |
color |
Solid background colour for all pages hex |
setPageBorder() option |
Type | Description |
|---|---|---|
:style |
String | "single", "double", "thick", "dashed", "dotted", "shadow" |
:color |
String | Border colour hex. Default: 000000 |
:size |
Number | Width in eighths of a point. 24 = 3pt |
:space |
Number | Gap from page edge in points. Default: 24 |
:offsetFrom |
String | "page" or "text" |
:sides |
List | ["top","bottom","left","right"]. Default: all four |
# Simple border
doc.setSimplePageBorder()
# Custom — top and bottom only, thick blue
doc.setPageBorder([
:style = "single",
:color = "1E6BB5",
:size = 48,
:sides = ["top", "bottom"]
])
# Light blue background
doc.setPageBackground("EBF3FB")| Method | Parameters | Description |
|---|---|---|
addFootnote() |
text, footnoteContent, options |
Inline text with a superscript footnote reference |
addEndnote() |
text, endnoteContent, options |
Inline text with an endnote reference |
registerFootnote() |
footnoteContent |
Register a note, return its ID for use in addRichParagraph |
registerEndnote() |
endnoteContent |
Register an endnote, return its ID |
The footnoteContent / endnoteContent argument accepts either:
- A plain string — simple unformatted note body
- A list of
[text, options]run pairs — rich formatted body with per-run bold, italic, colour, size, and font (same format asaddRichParagraph)
# Plain-text footnote (backward-compatible)
doc.addFootnote(
"Ring is a multi-paradigm language.",
"Ring Programming Language. ring-lang.github.io",
[]
)
# Rich footnote — bold author name, coloured URL, italic date
doc.addFootnote(
"See the official documentation.",
[
[" See: ", []],
["Mahmoud", [:bold=true, :color="1B5E20"]],
[", Ring Language, ", []],
["2016-2026", [:italic=true, :color="666666"]],
[". ring-lang.github.io", []]
],
[]
)
# Rich endnote — italic title with underline, bold source
doc.addEndnote(
"See the OOXML standard.",
[
["Source: ", []],
["OOXML Standard ECMA-376", [:italic=true, :underline=true]],
[", 5th edition — ", []],
["ISO/IEC 29500", [:bold=true, :color="1A237E"]]
],
[]
)Tip: Use
registerFootnote(content)to attach a note to a specific run insideaddRichParagraph()via:footnoteIdon the run options.
doc.addHyperlink("Ring Language Website", "http://ring-lang.github.io")
doc.addHyperlink("Send Email", "mailto:info@ring-lang.github.io")| Method | Parameters | Description |
|---|---|---|
addBookmarkedParagraph() |
name, text, options |
Paragraph with an invisible named bookmark anchor |
addBookmark() |
name |
Zero-width anchor inserted at the current position |
# Bookmark an entire heading
doc.addBookmarkedParagraph("bm_intro", "1. Introduction", [:style = "Heading1"])
# Bookmark a specific point (no text)
doc.addParagraph("See the results ", NULL)
doc.addBookmark("results_anchor")addCrossRef() inserts a Word REF field that resolves to the page number of the named
bookmark when the user presses F9.
doc.addCrossRef("bm_intro", "see Introduction on page ")
# → renders as: 'see Introduction on page 3' after F9addTableOfContents() inserts a Word TOC field that collects all Heading 1–3 paragraphs.
doc.addTableOfContents("Table of Contents")
doc.addPageBreak()
doc.addHeading("Chapter 1", 1)
doc.addHeading("1.1 Background", 2)Note: Press F9 in Word to populate page numbers. The field uses
TOC \o "1-3" \h \z \uand collects Headings 1 through 3.
# After an image:
doc.addImageCentered("arch.png", 12, 8)
doc.addFigureCaption("Ring VM architecture overview")
# → renders as: Figure 1 — Ring VM architecture overview
# Above or below a table:
doc.addTableCaption("Feature comparison across Ring versions")
doc.addTable(data, options)
# → renders as: Table 1 — Feature comparison across Ring versionsCaption entries are pre-populated from captions already added — lists are visible immediately on open without pressing F9.
# Place these early in the document (before the figures/tables)
doc.addTableOfFigures("List of Figures")
doc.addTableOfTables("List of Tables")
doc.addPageBreak()
# For any custom SEQ label (Equation, Chart, Listing, etc.):
doc.addTableOfSeq("Equation", "List of Equations")Tip: Press F9 in Word to refresh page numbers. Caption text is always visible immediately; only the page numbers need updating.
# TOC-style dot leader:
tocTabs = [:tabStops = [[:pos=8500, :align="right", :leader="dot"]]]
doc.addTabbedParagraph(["Introduction", "1"], tocTabs)
doc.addTabbedParagraph(["Chapter 1 — Background", "3"], tocTabs)
doc.addTabbedParagraph(["Chapter 2 — Results", "7"], tocTabs)
# Price list:
priceTabs = [:tabStops = [[:pos=8500, :align="right", :leader="dot"]]]
doc.addTabbedParagraph(["Ring Language", "$0.00 (Open Source)"], priceTabs)
doc.addTabbedParagraph(["DOCXLib", "$0.00 (Open Source)"], priceTabs)
# Multi-column layout:
multiTabs = [:tabStops = [
[:pos=3000, :align="center", :leader="none"],
[:pos=6000, :align="left", :leader="none"],
[:pos=9000, :align="right", :leader="none"]
]]
doc.addTabbedParagraph(["Name", "Role", "Score", "Grade"], multiTabs)| Tab stop option | Values | Description |
|---|---|---|
:pos |
Number (twips) | Distance from left margin. 1440 twips = 1 inch |
:align |
"left" / "center" / "right" / "decimal" |
Alignment at the stop |
:leader |
"none" / "dot" / "hyphen" / "underscore" |
Fill character between stops |
doc.enableLineNumbers([
:start = 1, # first line number
:step = 5, # show number every 5 lines
:restart = "newPage" # restart on each page
])
doc.addParagraph("This paragraph shows line numbers.", NULL)
doc.addParagraph("This one is excluded.", [:suppressLineNumbers=true])
doc.disableLineNumbers() # turn off for remaining paragraphsenableLineNumbers option |
Type | Description |
|---|---|---|
:start |
Number | First line number. Default: 1 |
:step |
Number | Show number every N lines. Default: 1 |
:distance |
Number | Gap from text margin in twips. Default: 360 |
:restart |
String | "newPage", "newSection", "continuous" |
| Method | Parameters | Description |
|---|---|---|
addCommentedParagraph() |
text, commentText, options |
Paragraph with a comment balloon attached |
addComment() |
text, commentText, author |
Shortcut with explicit author name |
doc.addCommentedParagraph(
"The Ring VM is stack-based and written in C.",
"Please verify against the source",
[:commentAuthor = "Mahmoud",
:commentDate = "2026-03-15T09:00:00Z"]
)
# Shortcut form:
doc.addComment("See Table 3 for benchmark data.",
"Check these numbers against the latest run.",
"Reviewer")Mail merge fields create .docx templates that Word merges with data sources. Each
MERGEFIELD placeholder is replaced with a real value during the merge.
# Formal letter template
doc.addMergeFieldParagraph([[:field="RecipientName"]], [:bold=true, :size=13])
doc.addMergeFieldParagraph([[:field="CompanyName"]], NULL)
doc.addMergeFieldParagraph([[:field="Address"]], NULL)
doc.addMergeFieldParagraph([[:field="City"], ", ", [:field="Country"]], NULL)
doc.addParagraph("", NULL)
doc.addMergeFieldParagraph(["Dear ", [:field="Salutation"], " ", [:field="LastName"], ","], NULL)
# Batch template for invoice fields:
doc.addMergeTemplate([
["Invoice #: ", [:field="InvoiceNo"]],
["Date: ", [:field="InvoiceDate"]],
["Client: ", [:field="ClientName"]],
["Amount Due: $", [:field="TotalAmount"]]
])Note: In
addMergeFieldParagraph(), pass a plain string for literal text and[:field="Name"]for aMERGEFIELDplaceholder. They can be freely mixed.
Content controls embed interactive form elements in the DOCX. Users can fill in the form without enabling macros.
doc.addCheckbox("I accept the Terms and Conditions", true) # pre-checked
doc.addCheckbox("Subscribe to the Ring newsletter", false) # unchecked
doc.addCheckbox("Enable two-factor authentication", true)
doc.addCheckbox("Allow anonymous usage statistics", false)doc.addDropdown("Country",
["Saudi Arabia", "Egypt", "UAE", "Kuwait", "Jordan", "Morocco", "Other"],
"Saudi Arabia" # default selection
)
doc.addDropdown("Experience Level",
["Beginner", "Intermediate", "Advanced", "Expert"],
"Intermediate"
)doc.addTextInput("Full Name", "", "Enter your full name")
doc.addTextInput("Email", "", "name@example.com")
doc.addTextInput("Organisation", "", "Company or university")
doc.addTextInput("Comments", "Pre-filled default", NULL)Inline DrawingML shapes embedded in the document flow. All shapes support fill colour, border, and an optional text label.
| Method | Shape type | Notes |
|---|---|---|
addRect(options) |
Rectangle | Use :rounded=true for rounded corners |
addEllipse(options) |
Ellipse / circle | Equal width and height for a circle |
addDiamond(options) |
Diamond / rhombus | Useful for flowchart decision nodes |
addShape("triangle", options) |
Right triangle | |
addLine(options) |
Horizontal rule | Thin coloured rect; use :height for thickness |
addShape(type, options) |
Any of the above | Generic form |
| Option | Type | Description |
|---|---|---|
:width |
Number | Width in cm. Default: 5 |
:height |
Number | Height in cm. Default: 3 (line: 0.1) |
:fillColor |
String | Fill colour hex. Default: 4472C4 |
:lineColor |
String | Border colour hex. Default: 2E4E7E |
:lineWidth |
Number | Border width in pt. Default: 1 |
:noFill |
true/false | Transparent shape (no fill) |
:noBorder |
true/false | No border |
:rounded |
true/false | Rounded corners (rect only) |
:text |
String | Label text inside the shape |
:textColor |
String | Text colour hex. Default: FFFFFF |
:textBold |
true/false | Bold text label. Default: true |
:textSize |
Number | Text size in pt. Default: 11 |
:align |
String | Outer paragraph alignment: "left", "center", "right" |
# Solid rectangle with white text
doc.addRect([:width=8, :height=1.8, :fillColor="1E6BB5",
:text="Section Header", :textColor="FFFFFF"])
# Rounded border-only box
doc.addRect([:width=6, :height=1.4, :noFill=true,
:lineColor="C00000", :lineWidth=2,
:text="Border Only", :textColor="C00000", :rounded=true])
# Thin horizontal rule
doc.addLine([:width=14, :height=0.08, :fillColor="4472C4", :noBorder=true])
# Circle with text
doc.addEllipse([:width=3, :height=3, :fillColor="7030A0",
:text="75%", :textSize=18])addTextBox() inserts a floating VML text box positioned absolutely relative to the page.
doc.addTextBox("This content floats beside the main body text.", [
:width = 6, # cm
:height = 4, # cm
:top = 2, # cm from top of page
:left = 10.5, # cm from left of page
:bgColor = "F0F4FF",
:borderColor = "1E6BB5",
:bold = true,
:fontSize = 11,
:align = "left"
])| Method | Parameters | Description |
|---|---|---|
setDocumentRTL() |
bEnable |
Enable document-wide RTL — flips paragraph alignment and list indents |
addRTLParagraph() |
text, options |
Single RTL paragraph in an otherwise LTR document |
# Full RTL document (Arabic)
doc.setDocumentRTL(true)
doc.addHeading("مرحباً بكم في DOCXLib", 1)
doc.addParagraph("هذا مستند منشأ باستخدام لغة Ring.", NULL)
# Single RTL paragraph inside an LTR document
doc.addRTLParagraph("النص العربي هنا", NULL)if doc.save("output.docx")
? "Document saved successfully."
else
? "Error: save() failed."
oksave() returns true on success. It automatically:
- Creates a temporary working directory
- Writes all XML parts:
document.xml,styles.xml,settings.xml,numbering.xml,fontTable.xml,footnotes.xml,endnotes.xml,comments.xml, header/footer files, andtheme/theme1.xml(when a theme is set) - Copies all referenced image files into
word/media/ - Assembles
[Content_Types].xmland all.relsrelationship files - Creates the ZIP archive (DOCX format) using Ring's built-in zip functions
- Cleans up the temporary directory
Chart types are stored as pure XML inside the DOCX package with inline data caches, so charts are completely self-contained with no linked spreadsheet.
| Method | Chart Type | Best Used For |
|---|---|---|
addColumnChart(title, cats, series, opts) |
Vertical bars | Category comparisons across groups |
addBarChart(title, cats, series, opts) |
Horizontal bars | Long category labels |
addLineChart(title, cats, series, opts) |
Lines / trends | Time-series, continuous data |
addAreaChart(title, cats, series, opts) |
Filled areas | Cumulative or stacked trends |
addPieChart(title, cats, series, opts) |
Pie slices | Proportions — uses first series only |
addDoughnutChart(title, cats, series, opts) |
Hollow-centre pie | Proportions with inner space |
addChart(type, title, cats, series, opts) |
Any type above | Generic entry point |
| Method | Chart Type | Best Used For |
|---|---|---|
addScatterChart(title, series, opts) |
XY scatter | Correlation, scientific data, irregular X spacing |
addBubbleChart(title, series, opts) |
Bubble | Three-variable data — X, Y, and a size dimension |
| Key | Type | Required | Description |
|---|---|---|---|
:name |
String | Yes | Series label shown in the legend |
:values |
List | Yes | Numeric values, one per category |
:color |
String | No | Hex colour override, e.g. "4472C4" |
| Key | Type | Required | Description |
|---|---|---|---|
:name |
String | Yes | Series label shown in the legend |
:xValues |
List | Yes | X-axis numeric values |
:yValues |
List | Yes | Y-axis numeric values (same length as :xValues) |
:color |
String | No | Hex colour override |
| Key | Type | Required | Description |
|---|---|---|---|
:name |
String | Yes | Series label shown in the legend |
:xValues |
List | Yes | X-axis numeric values |
:yValues |
List | Yes | Y-axis numeric values |
:sizes |
List | Yes | Bubble size values — positive numbers, relative scale |
:color |
String | No | Hex colour override |
If :color is omitted for any series type the library cycles through an 8-colour
Office palette automatically.
| Option | Type | Default | Description |
|---|---|---|---|
:widthCm |
Number | 14 |
Display width in centimetres |
:heightCm |
Number | 9 |
Display height in centimetres |
:centered |
Bool | true |
Centre chart horizontally on the page |
:showLegend |
Bool | true |
Show or hide the legend |
:legendPos |
String | "r" |
Legend position: "r" "b" "t" "l" |
:showDataLabels |
Bool | false |
Show value labels on each data point |
| Option | Type | Default | Description |
|---|---|---|---|
:yAxisMin |
Number | auto | Force value axis lower bound (e.g. 0 to start at zero) |
:yAxisMax |
Number | auto | Force value axis upper bound |
:yAxisFormat |
String | "General" |
Number format for value axis labels — see table below |
:yAxisTitle |
String | "" |
Rotated label alongside the value axis |
:xAxisTitle |
String | "" |
Label below the category axis (or X axis for scatter/bubble) |
:showGridlines |
Bool | true |
Show major gridlines on the value axis |
:showDataTable |
Bool | false |
Show source data in a table below the chart (column/bar/line/area only) |
:dataTableShowKeys |
Bool | true |
Include colour legend keys in the data table |
| Format string | Example output | Use case |
|---|---|---|
"General" |
1234567 |
Default — no formatting |
"#,##0" |
1,234,567 |
Large integers with thousands separator |
"$#,##0" |
$1,234,567 |
Currency (whole dollars) |
"$#,##0.00" |
$1,234,567.89 |
Currency with cents |
"0%" |
32% |
Percentage (value 0.32 → 32%) |
"0.0%" |
32.5% |
Percentage with one decimal |
"0.00" |
3.14 |
Two decimal places |
"0.0" |
3.1 |
One decimal place |
"0.00E+00" |
3.14E+06 |
Scientific notation |
Note: These are standard OOXML/Excel number format strings. Any Excel format string is accepted — the library passes it through verbatim to the XML.
| Option | Type | Default | Description |
|---|---|---|---|
:grouping |
String | "clustered" |
Bar/column/area: "clustered" "stacked" "percentstacked" |
:smooth |
Bool | false |
Line chart only — bezier curves |
:colors |
List | (palette) | Override full palette with a list of hex strings |
| Option | Type | Default | Description |
|---|---|---|---|
:markerStyle |
String | "circle" |
"circle" "square" "diamond" "triangle" "x" "star" "dot" "dash" "none" |
:markerSize |
Number | 7 |
Marker size in half-points |
:lines |
Bool | false |
Connect points with lines |
:smooth |
Bool | false |
Bezier curves on lines (requires :lines = true) |
:xAxisMin |
Number | auto | Force X axis lower bound |
:xAxisMax |
Number | auto | Force X axis upper bound |
:xAxisFormat |
String | "General" |
Number format for X axis labels |
:xAxisTitle |
String | "" |
X axis label |
:yAxisTitle |
String | "" |
Y axis label |
| Option | Type | Default | Description |
|---|---|---|---|
:bubble3D |
Bool | false |
Render bubbles with 3-D specular shading |
:xAxisMin |
Number | auto | Force X axis lower bound |
:xAxisMax |
Number | auto | Force X axis upper bound |
:xAxisFormat |
String | "General" |
Number format for X axis labels |
:xAxisTitle |
String | "" |
X axis label |
:yAxisTitle |
String | "" |
Y axis label |
Column chart — three series, data labels:
doc.addColumnChart(
"Quarterly Revenue vs Costs ($K)",
["Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024"],
[
[:name = "Revenue", :values = [320, 410, 380, 490]],
[:name = "Costs", :values = [210, 255, 230, 290]],
[:name = "Profit", :values = [110, 155, 150, 200]]
],
[:widthCm = 14, :heightCm = 9, :showDataLabels = true, :legendPos = "b"])Line chart — smooth curves:
doc.addLineChart(
"Monthly Website Traffic",
["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
[
[:name = "2023", :values = [12000, 14500, 13200, 16800, 18000, 17400]],
[:name = "2024", :values = [15000, 18200, 17500, 21000, 23500, 22800]]
],
[:smooth = true, :legendPos = "r"])Pie chart — percent labels:
doc.addPieChart(
"Product Revenue Share",
["Ring", "PWCT", "Supernova", "PWCT2"],
[[:name = "Revenue ($K)", :values = [450, 280, 160, 110]]],
[:showDataLabels = true])Stacked column — :grouping option:
doc.addColumnChart(
"Revenue by Product",
["Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024"],
[
[:name = "Ring", :values = [120, 155, 140, 180]],
[:name = "PWCT", :values = [80, 95, 90, 110]],
[:name = "Supernova", :values = [60, 75, 70, 90]]
],
[:grouping = "stacked"])Scatter chart — markers only (note: NULL for categories, :xValues/:yValues in series):
doc.addScatterChart(
"Study Hours vs Exam Score",
[
[:name = "Cohort A",
:xValues = [2, 3, 4, 5, 6, 7, 8, 9, 10],
:yValues = [45, 52, 60, 67, 73, 79, 84, 88, 93]],
[:name = "Cohort B",
:xValues = [1, 2, 4, 5, 7, 8, 9, 10, 11],
:yValues = [38, 48, 55, 63, 70, 75, 81, 86, 90]]
],
[:markerStyle = "circle", :xAxisTitle = "Study Hours", :yAxisTitle = "Score (%)"])Scatter chart — smooth lines + markers:
doc.addScatterChart(
"Algorithm Complexity",
[
[:name = "O(n log n)", :xValues = [100, 500, 1000, 5000, 10000],
:yValues = [0.8, 5.2, 11.5, 68.3, 145.2], :color = "4472C4"],
[:name = "O(n^2)", :xValues = [100, 500, 1000, 5000, 10000],
:yValues = [1.2, 28.5, 112.4, 2802.5, 11210.0], :color = "C00000"]
],
[:lines = true, :smooth = true, :markerStyle = "diamond",
:xAxisTitle = "Input Size (n)", :yAxisTitle = "Time (ms)"])Scatter chart — lines only, no markers:
doc.addScatterChart(
"Sine vs Cosine Wave",
[
[:name = "sin(x)", :xValues = aX, :yValues = aSin, :color = "2E75B6"],
[:name = "cos(x)", :xValues = aX, :yValues = aCos, :color = "70AD47"]
],
[:lines = true, :smooth = true, :markerStyle = "none"])Bubble chart — flat shading (each product is its own series for distinct colours):
doc.addBubbleChart(
"Product Portfolio — Share / Growth / Revenue",
[
[:name = "Ring", :xValues = [35], :yValues = [22], :sizes = [450], :color = "4472C4"],
[:name = "PWCT", :xValues = [28], :yValues = [15], :sizes = [280], :color = "ED7D31"],
[:name = "Supernova", :xValues = [18], :yValues = [31], :sizes = [160], :color = "70AD47"]
],
[:xAxisTitle = "Market Share (%)", :yAxisTitle = "Growth (%)"])Bubble chart — 3-D shading, bubble-size labels, multiple points per series:
doc.addBubbleChart(
"Research Projects — Team / Duration / Impact",
[
[:name = "Project Alpha",
:xValues = [3, 5, 8, 12], :yValues = [6, 9, 14, 18],
:sizes = [45, 120, 280, 500], :color = "1F3864"],
[:name = "Project Beta",
:xValues = [2, 4, 7, 10], :yValues = [4, 7, 11, 15],
:sizes = [30, 90, 200, 380], :color = "C00000"]
],
[:bubble3D = true, :showDataLabels = true,
:xAxisTitle = "Team Size", :yAxisTitle = "Months"])Column chart — fixed axis bounds and currency format:
doc.addColumnChart(
"Regional Revenue vs Target",
["North", "South", "East", "West", "Central"],
[
[:name = "Actual", :values = [1250000, 980000, 1420000, 1100000, 870000]],
[:name = "Target", :values = [1200000, 1050000, 1350000, 1150000, 950000]]
],
[:yAxisMin = 0, :yAxisMax = 1800000,
:yAxisFormat = "$#,##0",
:yAxisTitle = "Revenue (USD)", :xAxisTitle = "Region",
:showGridlines = false, :legendPos = "b"])Line chart — percentage format on value axis:
doc.addLineChart(
"Gross Margin Trend",
["Q1", "Q2", "Q3", "Q4"],
[
[:name = "2023", :values = [0.38, 0.41, 0.39, 0.43]],
[:name = "2024", :values = [0.41, 0.44, 0.42, 0.46]]
],
[:yAxisMin = 0, :yAxisMax = 0.6,
:yAxisFormat = "0.0%",
:yAxisTitle = "Gross Margin", :xAxisTitle = "Quarter"])Scatter chart — fixed bounds with formatted numeric axes:
doc.addScatterChart(
"Ad Spend vs Revenue",
[
[:name = "2024",
:xValues = [50000, 100000, 200000, 350000],
:yValues = [210000, 430000, 870000, 1500000]]
],
[:xAxisMin = 0, :xAxisMax = 400000,
:yAxisMin = 0, :yAxisMax = 2000000,
:xAxisFormat = "$#,##0", :yAxisFormat = "$#,##0",
:xAxisTitle = "Ad Spend (USD)", :yAxisTitle = "Revenue (USD)",
:markerStyle = "circle", :lines = true, :smooth = true])Pie and doughnut charts render only the first series. If more than one series is passed, DOCXLib prints a warning to the console and silently discards the extra series. Use a column or bar chart when you need to compare multiple series.
# WRONG — triggers warning; only "2023" series will appear in the chart
doc.addPieChart("Sales", cats, [
[:name = "2023", :values = [380, 240, 140, 90]],
[:name = "2024", :values = [450, 280, 160, 110]] # ignored
], [])
# CORRECT — grouped column for side-by-side comparison
doc.addColumnChart("Sales 2023 vs 2024", cats, [
[:name = "2023", :values = [380, 240, 140, 90]],
[:name = "2024", :values = [450, 280, 160, 110]]
], [:legendPos = "b"])Each chart adds three components to the DOCX package automatically:
| File | Purpose |
|---|---|
word/charts/chartN.xml |
Full chart definition — data stored inline as XML caches; no spreadsheet needed |
word/charts/_rels/chartN.xml.rels |
Chart relationship manifest (no external dependencies) |
Drawing reference in document.xml |
<c:chart r:id="rIdN"/> anchors the chart in the document body |
[Content_Types].xml receives one Override entry per chart automatically.
Category-based charts use <c:cat> + <c:val> for series data. Scatter and bubble
charts use <c:xVal> + <c:yVal> (and <c:bubbleSize> for bubble) instead —
both axes are <c:valAx> rather than <c:catAx> + <c:valAx>.
Compatibility: Charts render in Word 2016+, Microsoft 365, and LibreOffice 7+. The DrawingML chart schema (
http://schemas.openxmlformats.org/drawingml/2006/chart) is part of the ECMA-376 standard.
load "docxlib.ring"
doc = new WordWriter()
doc.setTitle("Ring Language Performance Study")
doc.setAuthor("Mahmoud")
doc.setPageSize("a4")
doc.setTheme("Blue")
# Header / footer
doc.setFirstPageDifferent(true)
doc.setFirstPageHeader("") # blank on cover
doc.setHeader("Ring Language Performance Study")
doc.showPageNumbers("right")
# Cover page
doc.addTitle("Ring Language Performance Study")
doc.addSubtitle("A Comparative Analysis — v1.11.0 Edition")
doc.addPageBreak()
# Front matter
doc.addTableOfContents("Table of Contents")
doc.addTableOfFigures("List of Figures")
doc.addTableOfTables("List of Tables")
doc.addPageBreak()
# Chapter 1
doc.addHeading("1. Introduction", 1)
doc.addParagraph("Ring is a multi-paradigm language designed for application development.", NULL)
doc.addFootnote("See ring-lang.github.io", "Ring official website.", NULL)
doc.addParagraph("", NULL)
doc.addHeading("1.1 Methodology", 2)
doc.addBulletList(["Benchmark suite", "Reference VM", "Metrics collected"])
doc.addParagraph("", NULL)
# Image + caption
doc.addImageCentered("chart.png", 12, 7)
doc.addFigureCaption("Benchmark results across five test configurations")
doc.addParagraph("", NULL)
# Table + caption
doc.addTableCaption("Raw timing data in milliseconds")
doc.addTable([
["Test Case", "Ring", "Lang2", "Lang3"],
["Fibonacci", "42", "180", "95" ],
["Sort 10k", "18", "72", "38" ],
["File I/O", "55", "140", "60" ]
], [
:headerRow = true,
:borderStyle = "single",
:headerBgColor = "1E6BB5",
:evenRowBgColor = "DEEAF1"
])
doc.save("report.docx")A full template-based mail merge engine. Define a template once with setMergeTemplate(), then call mergeRecord() or mergeAll() to produce personalised documents for any number of records.
| Method | Description |
|---|---|
setMergeTemplate(template) |
Register a template definition (list of items) |
clearMergeTemplate() |
Remove the registered template |
mergeRecord(data) |
Render the template once, substituting data into all {{FIELD}} tokens |
mergeAll(dataList, sep) |
Render for every record in dataList; insert separator between records |
mergeAll separator values: "pagebreak" (default) · "emptyline" · "line" · "none"
A template is a Ring list. Each item is either a plain string or an associative list:
| Item type | Description |
|---|---|
"Dear {{FirstName}}," |
Plain string — rendered as a paragraph; {{FIELD}} tokens filled |
[:type="heading", :level=N, :text="{{Field}} Report"] |
Heading at level 1–6 |
[:type="paragraph", :text="...", :options=[...]] |
Paragraph with full formatting options |
[:type="table", :data=[...], :options=[...]] |
Table — {{FIELD}} tokens in cells are filled |
[:type="pagebreak"] |
Page break |
[:type="emptyline"] |
Empty paragraph |
{{FIELD}} tokens work in all text fields including table cells. Unknown fields are left as-is.
A data record is a Ring associative list:
data = [
:FirstName = "Alice",
:LastName = "Smith",
:Score = "94"
]mergeAll() takes a list of such records:
dataList = [
[:Name = "Alice", :Score = "94"],
[:Name = "Bob", :Score = "78"]
]Simple letter — three recipients, page break between each:
doc.setMergeTemplate([
[:type = "paragraph", :text = "{{Date}}", :options = [:align = "right"]],
[:type = "emptyline"],
"Dear {{Salutation}} {{LastName}},",
[:type = "emptyline"],
[:type = "paragraph",
:text = "Your application to {{ProgramName}} has been accepted. " +
"Your enrolment number is {{EnrolmentNumber}}."],
[:type = "emptyline"],
[:type = "paragraph", :text = "Yours sincerely,"],
[:type = "paragraph", :text = "Mahmoud", :options = [:bold = true]]
])
recipients = [
[:Date = "1 March 2026", :Salutation = "Ms", :LastName = "Benali",
:ProgramName = "Ring MSc", :EnrolmentNumber = "MSC-2026-001"],
[:Date = "1 March 2026", :Salutation = "Mr", :LastName = "Mendes",
:ProgramName = "Ring MSc", :EnrolmentNumber = "MSC-2026-002"]
]
doc.mergeAll(recipients, "pagebreak")
doc.clearMergeTemplate()Invoice template with an embedded table:
doc.setMergeTemplate([
[:type = "heading", :level = 1, :text = "INVOICE #{{InvoiceNum}}"],
[:type = "table",
:data = [
["Bill To", "{{ClientName}}", "Date", "{{InvoiceDate}}"],
["Address", "{{Address}}", "Due", "{{DueDate}}"]
],
:options = [:borderStyle = "single", :colWidths = [3.5, 5.0, 2.5, 5.0]]],
[:type = "emptyline"],
[:type = "paragraph",
:text = "Total Due: {{Total}}", :options = [:bold = true, :align = "right"]]
])
doc.mergeAll(invoices, "pagebreak")The :showDataTable = true option renders the chart's source data in a compact bordered table directly beneath the plot area — useful for printed reports where readers need exact values.
| Option | Type | Default | Description |
|---|---|---|---|
:showDataTable |
Bool | false |
Render source data table beneath the chart |
:dataTableShowKeys |
Bool | true |
Include colour legend keys in the leftmost column |
Supported chart types: column, bar, line, area Not applicable to: pie, doughnut, scatter, bubble
Column chart with data table and legend keys:
doc.addColumnChart(
"Quarterly Revenue",
["Q1", "Q2", "Q3", "Q4"],
[
[:name = "Ring", :values = [320000, 410000, 380000, 490000]],
[:name = "PWCT", :values = [210000, 265000, 245000, 305000]]
],
[:yAxisFormat = "$#,##0", :legendPos = "b",
:showDataTable = true, :dataTableShowKeys = true])Line chart — data table without colour keys:
doc.addLineChart(
"Monthly Active Users",
["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
[[:name = "Web", :values = [142, 158, 171, 185, 201, 218]],
[:name = "Mobile", :values = [89, 104, 118, 135, 152, 171]]],
[:smooth = true, :showDataTable = true, :dataTableShowKeys = false])Combined with chart style presets:
doc.setChartDefaults([
:widthCm = 14,
:legendPos = "b",
:showDataTable = true,
:dataTableShowKeys = true,
:yAxisFormat = "#,##0"
])
# Every subsequent chart automatically gets a data table
doc.addColumnChart("Revenue", quarters, series, [])
doc.addLineChart("Trend", months, trendSeries, [:smooth = true])
# Remove all chart defaults
doc.clearChartDefaults()WordReader (docxlib.ring) parses .docx files and provides:
- Structured access to the document content as Ring lists
- Semantic query methods —
getHeadings(),getTables(),getSectionLayouts(), etc. - Full round-trip —
toWriter()converts the parsed document back into aWordWriterthat you can modify and re-save
load "docxlib.ring"
reader = new WordReader("report.docx")
reader.loadDocx()
? reader.summary()
headings = reader.getHeadings()
for h in headings
? string(h[:level]) + ": " + h[:text]
next
reader.save("report_copy.docx")
reader.cleanup()| Method | Parameters | Description |
|---|---|---|
new WordReader(filePath) |
filePath : String |
Constructor — sets the source .docx path |
loadDocx() |
— | Unzip and parse the document. Call once after construction |
save(outputPath) |
outputPath : String |
Round-trip: parse → reconstruct → write |
cleanup() |
— | Delete the temporary extraction folder |
toWriter() |
— | Return a populated WordWriter from the parsed content |
loadDocx()resets all internal state each call, so oneWordReaderinstance can be reused across multiple files.
| Method | Returns | Description |
|---|---|---|
getFilePath() |
String | Source file path |
getTitle() |
String | Document title from core.xml |
getAuthor() |
String | Document author |
getDefaultFont() |
String | Body font name |
getDefaultFontSize() |
Number | Body font size in points |
getPageWidthCm() |
Number | Page width in cm |
getPageHeightCm() |
Number | Page height in cm |
getMarginTopCm() |
Number | Top margin in cm |
getMarginBottomCm() |
Number | Bottom margin in cm |
getMarginLeftCm() |
Number | Left margin in cm |
getMarginRightCm() |
Number | Right margin in cm |
getOrientation() |
String | "portrait" or "landscape" |
getHeaderText() |
String | Default header text |
getFooterText() |
String | Default footer text |
getEvenPageHeaderText() |
String | Even-page header |
getEvenPageFooterText() |
String | Even-page footer |
getFirstPageHeaderText() |
String | First-page header |
getFirstPageFooterText() |
String | First-page footer |
hasEvenAndOddHeaders() |
Bool | Even/odd header mode enabled |
hasFirstPageDifferent() |
Bool | First-page-different mode enabled |
hasPageBorder() |
Bool | Page border present |
getPageBorderStyle() |
String | Page border style |
getPageBorderColor() |
String | Page border colour hex |
getPageBorderSizePt() |
Number | Page border width in points |
getPageBgColor() |
String | Page background colour hex |
hasPageBackground() |
Bool | Solid page background present |
getColumnCount() |
Number | Number of columns in the document |
getColumnSpaceCm() |
Number | Column gap in cm |
getTOCTitle() |
String | Table of contents title |
hasTOC() |
Bool | TOC field present |
hasWatermark() |
Bool | Text watermark present |
getWatermarkText() |
String | Watermark text |
getWatermarkOptions() |
List | Watermark properties |
isDocumentRTL() |
Bool | Document-wide RTL enabled |
hasPageNumbers() |
Bool | Page number field present |
getPageNumberAlign() |
String | Page number alignment |
getCustomProperties() |
List | All custom document properties |
getCustomProperty(name) |
String | A single custom property by name |
? reader.summary() # human-readable content overview
reader.listBlocks() # numbered block list with type + preview
blocks = reader.getBlocks() # raw list of all parsed blocks| Method | Returns | Description |
|---|---|---|
getHeadings() |
List of [:level, :text, :style] |
All heading blocks (levels 1–6) |
getParagraphs() |
List of paragraph blocks | All body paragraphs with formatting |
getRTLParagraphs() |
List | Right-to-left paragraphs |
getCommentedParagraphs() |
List of [:text, :commentText, :commentAuthor] |
Paragraphs with attached comments |
getKeepNextParagraphs() |
List | Paragraphs with keep-with-next set |
getPageBreakBeforeParagraphs() |
List | Paragraphs preceded by a page break |
getBorderedParagraphs() |
List | Paragraphs with a border box |
getNamedStyleParagraphs(name) |
List | Paragraphs using a specific named paragraph style |
getOutlinedParagraphs() |
List | Paragraphs with an explicit outline level |
getNumberedHeadings() |
List | Headings that are part of a numbered list |
getTabbedParagraphs() |
List | Paragraphs containing tab characters |
getHiddenRuns() |
List of [:text] |
Runs with vanish (hidden text) |
getCapsRuns() |
List of [:text, :type] |
Runs with allCaps or smallCaps |
getDStrikeRuns() |
List of [:text] |
Runs with double-strikethrough |
getRunBorderRuns() |
List | Runs with a character-level border |
getRunLanguages() |
List of [:lang, :text] |
Runs with explicit language tags |
getCharStyleRuns() |
List of [:styleName, :text] |
Runs referencing a named character style |
getCharStyles() |
List | All character style names found in the document |
getTextBoxes() |
List of [:text] |
Floating text box content |
| Method | Returns | Description |
|---|---|---|
getListItems() |
List of [:text, :level, :isBullet] |
All list items with nesting level |
getListRestartPoints() |
List | List items where numbering restarts |
getHyperlinks() |
List of [:text, :url] |
All external hyperlinks |
getCaptions() |
List of [:text, :label, :seqNum] |
SEQ figure/table captions |
getBookmarks() |
List of [:name, :text] |
Named bookmark anchors |
| Method | Returns | Description |
|---|---|---|
getFootnotes() |
List of note blocks | All footnotes |
getEndnotes() |
List of note blocks | All endnotes |
Each note block contains:
block[:id] — note reference number
block[:refText] — anchor text in the document body
block[:noteText] — plain concatenated note body text
block[:runs] — list of formatted runs, each: [:text, :bold, :italic,
:underline, :color, :font, :size]
footnotes = reader.getFootnotes()
for fn in footnotes
? "Note " + fn[:id] + ": " + fn[:noteText]
for r in fn[:runs]
if r[:bold] = true ? " BOLD: " + r[:text] ok
next
next| Method | Returns | Description |
|---|---|---|
getComments() |
List of [:id, :text, :author] |
All comment entries |
getMergeFields() |
List of [:name] |
All MERGEFIELD entries |
getMergeFieldNames() |
List of Strings | MERGEFIELD names as a flat list |
getFields() |
List of [:fieldType, :fieldResult] |
All Word fields (DATE, PAGE, TOC, SEQ, …) |
getCharts() |
List of [:type, :title, :widthCm, :heightCm] |
Embedded chart metadata |
getChartData() |
List with full series data | Charts with categories and series values |
| Method | Returns | Description |
|---|---|---|
getFormFields() |
List | All form fields (any type) |
getCheckboxes() |
List of [:label, :checked] |
Checkbox controls |
getDropdowns() |
List of [:label, :choices, :default] |
Dropdown controls |
getTextInputs() |
List of [:label, :value] |
Text input controls |
| Method | Returns | Description |
|---|---|---|
getTables() |
List of table blocks | All tables with full row/cell data |
getTableLayouts() |
List of layout summaries | Dimension and style summary per table |
getTableStyle(blockIndex) |
List | Style info for a specific table |
getTableRowProperties() |
List | Row heights and header-repeat flags |
getNestedTables() |
List | Tables nested inside cell content |
getCellsWithBorders() |
List | Cells with explicit border overrides |
getCellsWithPadding() |
List | Cells with custom padding |
Table block structure:
block[:rows] — list of rows; each row is a list of cell dicts
block[:colWidths] — column widths in twips
block[:rowHeights] — per-row heights in twips (0 = auto)
block[:rowHRules] — per-row height rule strings ("atLeast" / "exact")
block[:rowIsHeader] — per-row boolean (tblHeader repeat flag)
block[:borderStyle] — table-level border style
block[:headerBg] — header background colour hex
block[:evenRowBg] — even-row stripe colour
Cell dict structure:
cell[:text] — concatenated plain text
cell[:runs] — list of runs: [:text, :bold, :italic, :color, :font,
:size, :lang, :styleName]
cell[:bgColor] — background colour hex
cell[:align] — text alignment
cell[:vAlign] — vertical alignment
cell[:colspan] — column span count
cell[:rowspan] — row span count
cell[:borderStyle] — legacy all-sides border style
cell[:borderSides] — list of per-side entries: [:side, :style, :color, :size]
cell[:textDir] — "btLr", "tbRl", or "" (normal)
cell[:padTop/Bottom/Left/Right] — padding in twips
| Method | Returns | Description |
|---|---|---|
getImages() |
List of image blocks | All inline and floating images |
getImagesWithAlt() |
List of [:path, :altText, :widthCm, :heightCm] |
Images with alt text |
getFloatingImages() |
List | Floating images with position and wrap data |
getFloatingImageWrapTypes() |
List of [:wrapType, :wrapSide] |
Wrap type summary |
getLandscapeSections() |
List | Sections using landscape orientation |
Image blocks include crop data when present:
block[:widthCm] — display width in cm
block[:heightCm] — display height in cm
block[:floating] — true for floating images
block[:altText] — accessibility description
block[:cropL] — left crop in percent
block[:cropR] — right crop in percent
block[:cropT] — top crop in percent
block[:cropB] — bottom crop in percent
| Method | Returns | Description |
|---|---|---|
getSectionLayouts() |
List | Per-section column, header/footer info |
getSectionBreaks() |
List | Section break points with type and column settings |
getSectionHeaders() |
List | Section-level header/footer text |
getSectionLayouts() entry structure:
item[:breakType] — "nextPage", "continuous", "evenPage", "oddPage"
item[:numColumns] — column count in this section
item[:columnSpaceCm] — column gap in cm
item[:sectHeader] — section-specific header text
item[:sectFooter] — section-specific footer text
getSectionBreaks() entry structure:
item[:breakType] — break type string
item[:numColumns] — column count after the break
item[:columnSpaceCm] — column spacing in cm
toWriter() rebuilds the entire parsed document as a WordWriter instance. You can
then add new content, modify page settings, or just call save() to produce a clean
copy.
reader = new WordReader("template.docx")
reader.loadDocx()
writer = reader.toWriter()
# Append new content
writer.addPageBreak()
writer.addHeading("Appendix", 1)
writer.addParagraph("Content appended after round-trip reconstruction.", [])
writer.save("extended_output.docx")
reader.cleanup()Round-trip fidelity table:
| Element | Preserved |
|---|---|
| Paragraphs | Text, bold/italic/underline/strike, colour, font, size, alignment, indent, spacing |
| Run properties | Language (:lang), character style (:charStyle), superscript/subscript |
| Headings 1–6 | Level and text |
| Lists | Bullet and numbered, nesting levels, restart points |
| Tables | All cells, merges, bg colours, per-side borders, row heights, text direction |
| Images | Inline and floating, dimensions, wrap type, alt text, crop percentages |
| Footnotes | Full run-level formatting (bold, italic, colour, size per run) |
| Endnotes | Full run-level formatting |
| Hyperlinks | URL and display text |
| Bookmarks | Name and position |
| Section breaks | Break type, column count, column spacing |
| Page properties | Size, margins, orientation, background, page border |
| Headers/Footers | Default, first-page, even-page |
| Paragraph control | keepNext, keepLines, widowControl=false, noHyphenate=true |
| Comments | Text and author |
| Fields | DATE, PAGE, TOC, SEQ, and other auto-updating fields |
| Watermarks | Text and image |
| RTL | RTL paragraphs and document RTL mode |
load "docxlib.ring"
reader = new WordReader("doc.docx") # load a file generated by DOCXLib
reader.loadDocx()
# Metadata
? reader.getTitle() ? reader.getAuthor() ? reader.getPageWidthCm()
# Overview
? reader.summary()
reader.listBlocks()
# Content
headings = reader.getHeadings()
tables = reader.getTables()
images = reader.getImages()
footnotes = reader.getFootnotes()
charts = reader.getCharts()
hyperlinks = reader.getHyperlinks()
listItems = reader.getListItems()
# Run intelligence
langs = reader.getRunLanguages()
styles = reader.getCharStyleRuns()
hiddenTx = reader.getHiddenRuns()
# Table detail
layouts = reader.getTableLayouts()
borders = reader.getCellsWithBorders()
rowProps = reader.getTableRowProperties()
# Layout
sections = reader.getSectionLayouts()
breaks = reader.getSectionBreaks()
# Form fields
boxes = reader.getCheckboxes()
drops = reader.getDropdowns()
inputs = reader.getTextInputs()
# Round-trip
reader.save("output.docx")
reader.cleanup()